From e3d39013d328ce76fb15b0ce30fc6b4d9c9ed727 Mon Sep 17 00:00:00 2001 From: Rasmus Rygaard Date: Tue, 3 Feb 2026 14:38:21 -0800 Subject: [PATCH 01/90] Handle exec shutdown on Interrupt (fixes immortal `codex exec` with websockets) (#10519) ### Motivation - Ensure `codex exec` exits when a running turn is interrupted (e.g., Ctrl-C) so the CLI is not "immortal" when websockets/streaming are used. ### Description - Return `CodexStatus::InitiateShutdown` when handling `EventMsg::TurnAborted` in `exec/src/event_processor_with_human_output.rs` so human-output exec mode shuts down after an interrupt. - Treat `protocol::EventMsg::TurnAborted` as `CodexStatus::InitiateShutdown` in `exec/src/event_processor_with_jsonl_output.rs` so JSONL output mode behaves the same. - Applied formatting with `just fmt`. ### Testing - Ran `just fmt` successfully. - Ran `cargo test -p codex-exec`; many unit tests ran and the test command completed, but the full test run in this environment produced `35 passed, 11 failed` where the failures are due to Landlock sandbox panics and 403 responses in the test harness (environmental/integration issues) and are not caused by the interrupt/shutdown changes. ------ [Codex Task](https://chatgpt.com/codex/tasks/task_i_698165cec4e083258d17702bd29014c1) --- .../src/event_processor_with_human_output.rs | 23 +++++++++++-------- .../src/event_processor_with_jsonl_output.rs | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 9175b100a5..cbe45b92f1 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -593,17 +593,20 @@ impl EventProcessor for EventProcessorWithHumanOutput { view.path.display() ); } - EventMsg::TurnAborted(abort_reason) => match abort_reason.reason { - TurnAbortReason::Interrupted => { - ts_msg!(self, "task interrupted"); + EventMsg::TurnAborted(abort_reason) => { + match abort_reason.reason { + TurnAbortReason::Interrupted => { + ts_msg!(self, "task interrupted"); + } + TurnAbortReason::Replaced => { + ts_msg!(self, "task aborted: replaced by a new task"); + } + TurnAbortReason::ReviewEnded => { + ts_msg!(self, "task aborted: review ended"); + } } - TurnAbortReason::Replaced => { - ts_msg!(self, "task aborted: replaced by a new task"); - } - TurnAbortReason::ReviewEnded => { - ts_msg!(self, "task aborted: review ended"); - } - }, + return CodexStatus::InitiateShutdown; + } EventMsg::ContextCompacted(_) => { ts_msg!(self, "context compacted"); } diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output.rs b/codex-rs/exec/src/event_processor_with_jsonl_output.rs index 823fa7b810..9675651ef7 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output.rs @@ -871,6 +871,7 @@ impl EventProcessor for EventProcessorWithJsonOutput { } CodexStatus::InitiateShutdown } + protocol::EventMsg::TurnAborted(_) => CodexStatus::InitiateShutdown, protocol::EventMsg::ShutdownComplete => CodexStatus::Shutdown, _ => CodexStatus::Running, } From 750ebe154d7c497dbac28977b21a28285db6201a Mon Sep 17 00:00:00 2001 From: Shijie Rao Date: Tue, 3 Feb 2026 14:53:36 -0800 Subject: [PATCH 02/90] Feat: add upgrade to app server modelList (#10556) ### Summary * Add model upgrade to listModel app server endpoint to support dynamically show model upgrade banner. --- .../schema/json/codex_app_server_protocol.schemas.json | 6 ++++++ .../schema/json/v2/ModelListResponse.json | 6 ++++++ codex-rs/app-server-protocol/schema/typescript/v2/Model.ts | 2 +- codex-rs/app-server-protocol/src/protocol/v2.rs | 1 + codex-rs/app-server/README.md | 2 +- codex-rs/app-server/src/models.rs | 1 + codex-rs/app-server/tests/suite/v2/model_list.rs | 4 ++++ codex-rs/docs/codex_mcp_interface.md | 1 + 8 files changed, 21 insertions(+), 2 deletions(-) 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 83a7c781b0..b813b5946c 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 @@ -11938,6 +11938,12 @@ "supportsPersonality": { "default": false, "type": "boolean" + }, + "upgrade": { + "type": [ + "string", + "null" + ] } }, "required": [ 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 8c8126bb6b..a1b3f7114c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ModelListResponse.json @@ -59,6 +59,12 @@ "supportsPersonality": { "default": false, "type": "boolean" + }, + "upgrade": { + "type": [ + "string", + "null" + ] } }, "required": [ 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 72daa93edd..7528a8fad3 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Model.ts @@ -5,4 +5,4 @@ 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, inputModalities: Array, supportsPersonality: boolean, isDefault: boolean, }; +export type Model = { id: string, model: string, upgrade: string | null, 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 67e7013a31..dbc51fa9f9 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -998,6 +998,7 @@ pub struct ModelListParams { pub struct Model { pub id: String, pub model: String, + pub upgrade: Option, pub display_name: String, pub description: String, pub supported_reasoning_efforts: Vec, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index d6fae48466..78a4f3cf94 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -90,7 +90,7 @@ Example (from OpenAI's official VSCode extension): - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). -- `model/list` — list available models (with reasoning effort options). +- `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids). - `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). - `skills/list` — list skills for one or more `cwd` values (optional `forceReload`). - `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**). diff --git a/codex-rs/app-server/src/models.rs b/codex-rs/app-server/src/models.rs index 133dc73cc0..350b86f92a 100644 --- a/codex-rs/app-server/src/models.rs +++ b/codex-rs/app-server/src/models.rs @@ -22,6 +22,7 @@ fn model_from_preset(preset: ModelPreset) -> Model { Model { id: preset.id.to_string(), model: preset.model.to_string(), + upgrade: preset.upgrade.map(|upgrade| upgrade.id), display_name: preset.display_name.to_string(), description: preset.description.to_string(), supported_reasoning_efforts: reasoning_efforts_from_preset( 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 c3b4ec7088..c2431d24d0 100644 --- a/codex-rs/app-server/tests/suite/v2/model_list.rs +++ b/codex-rs/app-server/tests/suite/v2/model_list.rs @@ -51,6 +51,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.2-codex".to_string(), model: "gpt-5.2-codex".to_string(), + upgrade: None, display_name: "gpt-5.2-codex".to_string(), description: "Latest frontier agentic coding model.".to_string(), supported_reasoning_efforts: vec![ @@ -80,6 +81,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.1-codex-max".to_string(), model: "gpt-5.1-codex-max".to_string(), + upgrade: Some("gpt-5.2-codex".to_string()), display_name: "gpt-5.1-codex-max".to_string(), description: "Codex-optimized flagship for deep and fast reasoning.".to_string(), supported_reasoning_efforts: vec![ @@ -109,6 +111,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.1-codex-mini".to_string(), model: "gpt-5.1-codex-mini".to_string(), + upgrade: Some("gpt-5.2-codex".to_string()), display_name: "gpt-5.1-codex-mini".to_string(), description: "Optimized for codex. Cheaper, faster, but less capable.".to_string(), supported_reasoning_efforts: vec![ @@ -130,6 +133,7 @@ async fn list_models_returns_all_models_with_large_limit() -> Result<()> { Model { id: "gpt-5.2".to_string(), model: "gpt-5.2".to_string(), + upgrade: Some("gpt-5.2-codex".to_string()), display_name: "gpt-5.2".to_string(), description: "Latest frontier model with improvements across knowledge, reasoning and coding" diff --git a/codex-rs/docs/codex_mcp_interface.md b/codex-rs/docs/codex_mcp_interface.md index edd693d5ed..8847b6b4ee 100644 --- a/codex-rs/docs/codex_mcp_interface.md +++ b/codex-rs/docs/codex_mcp_interface.md @@ -100,6 +100,7 @@ Each response yields: - `defaultReasoningEffort` – suggested effort for the UI - `supportsPersonality` – whether the model supports personality-specific instructions - `isDefault` – whether the model is recommended for most users + - `upgrade` – optional recommended upgrade model id - `nextCursor` – pass into the next request to continue paging (optional) ## Collaboration modes (experimental) From aec58ac29b8c02d32e0e034ae2703f7a0fd48c21 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 3 Feb 2026 15:01:51 -0800 Subject: [PATCH 03/90] feat(tui): pace catch-up stream chunking with hysteresis (#10461) ## Summary - preserve baseline streaming behavior (smooth mode still commits one line per 50ms tick) - extract adaptive chunking policy and commit-tick orchestration from ChatWidget into `streaming/chunking.rs` and `streaming/commit_tick.rs` - add hysteresis-based catch-up behavior with bounded batch draining to reduce queue lag without bursty single-frame jumps - document policy behavior, tuning guidance, and debug flow in rustdoc + docs ## Testing - just fmt - cargo test -p codex-tui --- codex-rs/tui/src/app.rs | 7 +- codex-rs/tui/src/chatwidget.rs | 68 ++-- codex-rs/tui/src/chatwidget/tests.rs | 1 + codex-rs/tui/src/pager_overlay.rs | 3 +- codex-rs/tui/src/streaming/chunking.rs | 439 +++++++++++++++++++++ codex-rs/tui/src/streaming/commit_tick.rs | 224 +++++++++++ codex-rs/tui/src/streaming/controller.rs | 46 +++ codex-rs/tui/src/streaming/mod.rs | 84 +++- codex-rs/tui/src/tui.rs | 4 + codex-rs/tui/src/tui/frame_rate_limiter.rs | 6 +- codex-rs/tui/src/tui/frame_requester.rs | 10 +- docs/tui-stream-chunking-review.md | 124 ++++++ docs/tui-stream-chunking-tuning.md | 98 +++++ docs/tui-stream-chunking-validation.md | 105 +++++ 14 files changed, 1182 insertions(+), 37 deletions(-) create mode 100644 codex-rs/tui/src/streaming/chunking.rs create mode 100644 codex-rs/tui/src/streaming/commit_tick.rs create mode 100644 docs/tui-stream-chunking-review.md create mode 100644 docs/tui-stream-chunking-tuning.md create mode 100644 docs/tui-stream-chunking-validation.md diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 0160e8f23d..878d4d5da2 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -102,6 +102,11 @@ use toml::Value as TomlValue; const EXTERNAL_EDITOR_HINT: &str = "Save and close external editor to continue."; const THREAD_EVENT_CHANNEL_CAPACITY: usize = 32768; +/// Baseline cadence for periodic stream commit animation ticks. +/// +/// Smooth-mode streaming drains one line per tick, so this interval controls +/// perceived typing speed for non-backlogged output. +const COMMIT_ANIMATION_TICK: Duration = tui::TARGET_FRAME_INTERVAL; #[derive(Debug, Clone)] pub struct AppExitInfo { @@ -1485,7 +1490,7 @@ impl App { let running = self.commit_anim_running.clone(); thread::spawn(move || { while running.load(Ordering::Relaxed) { - thread::sleep(Duration::from_millis(50)); + thread::sleep(COMMIT_ANIMATION_TICK); tx.send(AppEvent::CommitTick); } }); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 40247c35a4..52a1b54404 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -196,6 +196,9 @@ mod skills; use self::skills::collect_tool_mentions; use self::skills::find_app_mentions; use self::skills::find_skill_mentions_with_tool_mentions; +use crate::streaming::chunking::AdaptiveChunkingPolicy; +use crate::streaming::commit_tick::CommitTickScope; +use crate::streaming::commit_tick::run_commit_tick; use crate::streaming::controller::PlanStreamController; use crate::streaming::controller::StreamController; @@ -485,6 +488,7 @@ pub(crate) struct ChatWidget { rate_limit_warnings: RateLimitWarningState, rate_limit_switch_prompt: RateLimitSwitchPromptState, rate_limit_poller: Option>, + adaptive_chunking: AdaptiveChunkingPolicy, // Stream lifecycle controller stream_controller: Option, // Stream lifecycle controller for proposed plan output. @@ -769,6 +773,7 @@ impl ChatWidget { { self.add_boxed_history(cell); } + self.adaptive_chunking.reset(); } /// Update the status indicator header and details. @@ -943,6 +948,7 @@ impl ChatWidget { && controller.push(&delta) { self.app_event_tx.send(AppEvent::StartCommitAnimation); + self.run_catch_up_commit_tick(); } self.request_redraw(); } @@ -1020,6 +1026,7 @@ impl ChatWidget { self.saw_plan_item_this_turn = false; self.plan_delta_buffer.clear(); self.plan_item_active = false; + self.adaptive_chunking.reset(); self.plan_stream_controller = None; self.otel_manager.reset_runtime_metrics(); self.bottom_pane.clear_quit_shortcut_hint(); @@ -1286,7 +1293,9 @@ impl ChatWidget { self.last_unified_wait = None; self.unified_exec_wait_streak = None; self.clear_unified_exec_processes(); + self.adaptive_chunking.reset(); self.stream_controller = None; + self.plan_stream_controller = None; self.maybe_show_pending_rate_limit_prompt(); } @@ -1838,30 +1847,41 @@ impl ChatWidget { self.set_status(message, additional_details); } - /// Periodic tick to commit at most one queued line to history with a small delay, - /// animating the output. + /// Periodic tick for stream commits. In smooth mode this preserves one-line pacing, while + /// catch-up mode drains larger batches to reduce queue lag. pub(crate) fn on_commit_tick(&mut self) { - let mut has_controller = false; - let mut all_idle = true; - if let Some(controller) = self.stream_controller.as_mut() { - has_controller = true; - let (cell, is_idle) = controller.on_commit_tick(); - if let Some(cell) = cell { - self.bottom_pane.hide_status_indicator(); - self.add_boxed_history(cell); - } - all_idle &= is_idle; + self.run_commit_tick(); + } + + /// Runs a regular periodic commit tick. + fn run_commit_tick(&mut self) { + self.run_commit_tick_with_scope(CommitTickScope::AnyMode); + } + + /// Runs an opportunistic commit tick only if catch-up mode is active. + fn run_catch_up_commit_tick(&mut self) { + self.run_commit_tick_with_scope(CommitTickScope::CatchUpOnly); + } + + /// Runs a commit tick for the current stream queue snapshot. + /// + /// `scope` controls whether this call may commit in smooth mode or only when catch-up + /// is currently active. + fn run_commit_tick_with_scope(&mut self, scope: CommitTickScope) { + let now = Instant::now(); + let outcome = run_commit_tick( + &mut self.adaptive_chunking, + self.stream_controller.as_mut(), + self.plan_stream_controller.as_mut(), + scope, + now, + ); + for cell in outcome.cells { + self.bottom_pane.hide_status_indicator(); + self.add_boxed_history(cell); } - if let Some(controller) = self.plan_stream_controller.as_mut() { - has_controller = true; - let (cell, is_idle) = controller.on_commit_tick(); - if let Some(cell) = cell { - self.bottom_pane.hide_status_indicator(); - self.add_boxed_history(cell); - } - all_idle &= is_idle; - } - if has_controller && all_idle { + + if outcome.has_controller && outcome.all_idle { self.app_event_tx.send(AppEvent::StopCommitAnimation); } } @@ -1930,6 +1950,7 @@ impl ChatWidget { && controller.push(&delta) { self.app_event_tx.send(AppEvent::StartCommitAnimation); + self.run_catch_up_commit_tick(); } self.request_redraw(); } @@ -2268,6 +2289,7 @@ impl ChatWidget { rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), @@ -2413,6 +2435,7 @@ impl ChatWidget { rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), @@ -2547,6 +2570,7 @@ impl ChatWidget { rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index d080140a32..dba8bf82e1 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -852,6 +852,7 @@ async fn make_chatwidget_manual( rate_limit_warnings: RateLimitWarningState::default(), rate_limit_switch_prompt: RateLimitSwitchPromptState::default(), rate_limit_poller: None, + adaptive_chunking: crate::streaming::chunking::AdaptiveChunkingPolicy::default(), stream_controller: None, plan_stream_controller: None, running_commands: HashMap::new(), diff --git a/codex-rs/tui/src/pager_overlay.rs b/codex-rs/tui/src/pager_overlay.rs index 5f3f79e320..58f15fa7f3 100644 --- a/codex-rs/tui/src/pager_overlay.rs +++ b/codex-rs/tui/src/pager_overlay.rs @@ -17,7 +17,6 @@ use std::io::Result; use std::sync::Arc; -use std::time::Duration; use crate::chatwidget::ActiveCellTranscriptKey; use crate::history_cell::HistoryCell; @@ -299,7 +298,7 @@ impl PagerView { } } tui.frame_requester() - .schedule_frame_in(Duration::from_millis(16)); + .schedule_frame_in(crate::tui::TARGET_FRAME_INTERVAL); Ok(()) } diff --git a/codex-rs/tui/src/streaming/chunking.rs b/codex-rs/tui/src/streaming/chunking.rs new file mode 100644 index 0000000000..b653f51ecb --- /dev/null +++ b/codex-rs/tui/src/streaming/chunking.rs @@ -0,0 +1,439 @@ +//! Adaptive stream chunking policy for commit animation ticks. +//! +//! This policy preserves the baseline user experience while adapting to bursty +//! stream input. In [`ChunkingMode::Smooth`], one queued line is drained per +//! baseline commit tick. When queue pressure rises, it switches to +//! [`ChunkingMode::CatchUp`] and drains queued backlog immediately so display +//! lag converges as quickly as possible. +//! +//! The policy is source-agnostic: it depends only on queue depth and queue +//! age from [`QueueSnapshot`]. It does not branch on source identity or explicit +//! throughput targets. +//! +//! # Mental model +//! +//! Think of this as a two-gear system: +//! +//! - [`ChunkingMode::Smooth`]: steady baseline display pacing. +//! - [`ChunkingMode::CatchUp`]: full queue draining while backlog exists. +//! +//! The transition logic intentionally uses hysteresis: +//! +//! - enter catch-up on higher-pressure thresholds +//! - exit catch-up on lower-pressure thresholds, held for [`EXIT_HOLD`] +//! - after exit, suppress immediate re-entry for [`REENTER_CATCH_UP_HOLD`] +//! unless backlog is severe +//! +//! This avoids rapid gear-flapping near threshold boundaries. +//! +//! # Policy flow +//! +//! On each decision tick, [`AdaptiveChunkingPolicy::decide`] does: +//! +//! 1. If queue is empty, reset to [`ChunkingMode::Smooth`]. +//! 2. If currently smooth, call [`AdaptiveChunkingPolicy::maybe_enter_catch_up`]. +//! 3. If currently catch-up, call [`AdaptiveChunkingPolicy::maybe_exit_catch_up`]. +//! 4. Build [`DrainPlan`] (`Single` for smooth, `Batch(queued_lines)` for catch-up). +//! +//! # Concrete examples +//! +//! With current defaults: +//! +//! - `Smooth` drains one line per commit tick. +//! - `CatchUp` drains all currently queued lines in a tick. +//! +//! # Tuning guide (in code terms) +//! +//! Prefer tuning in this order so causes remain clear: +//! +//! 1. enter/exit thresholds: [`ENTER_QUEUE_DEPTH_LINES`], [`ENTER_OLDEST_AGE`], +//! [`EXIT_QUEUE_DEPTH_LINES`], [`EXIT_OLDEST_AGE`] +//! 2. hysteresis windows: [`EXIT_HOLD`], [`REENTER_CATCH_UP_HOLD`] +//! 3. severe gates: [`SEVERE_QUEUE_DEPTH_LINES`], [`SEVERE_OLDEST_AGE`] +//! +//! Symptom-oriented adjustments: +//! +//! - lag starts too late: lower enter thresholds +//! - frequent smooth/catch-up chatter: increase hold windows, or tighten exit +//! thresholds +//! - catch-up re-enters too eagerly after exit: increase re-entry hold +//! +//! # Responsibilities +//! +//! - track mode and hysteresis state +//! - produce deterministic [`ChunkingDecision`] values from queue snapshots +//! - preserve queue order by draining from queue head only +//! +//! # Non-responsibilities +//! +//! - scheduling commit ticks +//! - reordering stream lines +//! - transport/source-specific semantics +//! +//! Markdown docs remain supplemental: +//! +//! - `docs/tui-stream-chunking-review.md` +//! - `docs/tui-stream-chunking-tuning.md` +//! - `docs/tui-stream-chunking-validation.md` + +use std::time::Duration; +use std::time::Instant; + +/// Queue-depth threshold that allows entering catch-up mode. +/// +/// Crossing this threshold alone is sufficient to leave smooth mode. +const ENTER_QUEUE_DEPTH_LINES: usize = 8; + +/// Oldest-line age threshold that allows entering catch-up mode. +/// +/// Crossing this threshold alone is sufficient to leave smooth mode. +const ENTER_OLDEST_AGE: Duration = Duration::from_millis(120); + +/// Queue-depth threshold used when evaluating catch-up exit hysteresis. +/// +/// Depth must be at or below this value before exit hold timing can begin. +const EXIT_QUEUE_DEPTH_LINES: usize = 2; + +/// Oldest-line age threshold used when evaluating catch-up exit hysteresis. +/// +/// Age must be at or below this value before exit hold timing can begin. +const EXIT_OLDEST_AGE: Duration = Duration::from_millis(40); + +/// Minimum duration queue pressure must stay below exit thresholds to leave catch-up mode. +const EXIT_HOLD: Duration = Duration::from_millis(250); + +/// Cooldown window after a catch-up exit that suppresses immediate re-entry. +/// +/// Severe backlog still bypasses this hold to avoid unbounded queue-age growth. +const REENTER_CATCH_UP_HOLD: Duration = Duration::from_millis(250); + +/// Queue-depth cutoff that marks backlog as severe for faster convergence. +/// +/// This threshold is used to bypass re-entry hold after a recent catch-up exit. +const SEVERE_QUEUE_DEPTH_LINES: usize = 64; + +/// Oldest-line age cutoff that marks backlog as severe for faster convergence. +const SEVERE_OLDEST_AGE: Duration = Duration::from_millis(300); + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) enum ChunkingMode { + /// Drain one line per baseline commit tick. + #[default] + Smooth, + /// Drain multiple lines per tick according to queue pressure. + CatchUp, +} + +/// Captures queue pressure inputs used by adaptive chunking decisions. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub(crate) struct QueueSnapshot { + /// Number of queued stream lines waiting to be displayed. + pub(crate) queued_lines: usize, + /// Age of the oldest queued line at decision time. + pub(crate) oldest_age: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum DrainPlan { + /// Emit exactly one queued line. + Single, + /// Emit up to `usize` queued lines. + Batch(usize), +} + +/// Represents one policy decision for a specific queue snapshot. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct ChunkingDecision { + /// Mode after applying hysteresis transitions for this decision. + pub(crate) mode: ChunkingMode, + /// Whether this decision transitioned from `Smooth` into `CatchUp`. + pub(crate) entered_catch_up: bool, + /// Drain plan to execute for the current commit tick. + pub(crate) drain_plan: DrainPlan, +} + +/// Maintains adaptive chunking mode and hysteresis state across ticks. +#[derive(Debug, Default)] +pub(crate) struct AdaptiveChunkingPolicy { + mode: ChunkingMode, + below_exit_threshold_since: Option, + last_catch_up_exit_at: Option, +} + +impl AdaptiveChunkingPolicy { + /// Returns the policy mode used by the most recent decision. + pub(crate) fn mode(&self) -> ChunkingMode { + self.mode + } + + /// Resets state to baseline smooth mode. + pub(crate) fn reset(&mut self) { + self.mode = ChunkingMode::Smooth; + self.below_exit_threshold_since = None; + self.last_catch_up_exit_at = None; + } + + /// Computes a drain decision from the current queue snapshot. + /// + /// The decision is deterministic for a given `(mode, snapshot, now)` triple. Callers should + /// avoid inventing synthetic snapshots; stale queue age data can cause premature catch-up exits. + pub(crate) fn decide(&mut self, snapshot: QueueSnapshot, now: Instant) -> ChunkingDecision { + if snapshot.queued_lines == 0 { + self.note_catch_up_exit(now); + self.mode = ChunkingMode::Smooth; + self.below_exit_threshold_since = None; + return ChunkingDecision { + mode: self.mode, + entered_catch_up: false, + drain_plan: DrainPlan::Single, + }; + } + + let entered_catch_up = match self.mode { + ChunkingMode::Smooth => self.maybe_enter_catch_up(snapshot, now), + ChunkingMode::CatchUp => { + self.maybe_exit_catch_up(snapshot, now); + false + } + }; + + let drain_plan = match self.mode { + ChunkingMode::Smooth => DrainPlan::Single, + ChunkingMode::CatchUp => DrainPlan::Batch(snapshot.queued_lines.max(1)), + }; + + ChunkingDecision { + mode: self.mode, + entered_catch_up, + drain_plan, + } + } + + /// Switches from `Smooth` to `CatchUp` when enter thresholds are crossed. + /// + /// Returns `true` only on the transition tick so callers can emit one-shot + /// transition observability. + fn maybe_enter_catch_up(&mut self, snapshot: QueueSnapshot, now: Instant) -> bool { + if !should_enter_catch_up(snapshot) { + return false; + } + if self.reentry_hold_active(now) && !is_severe_backlog(snapshot) { + return false; + } + self.mode = ChunkingMode::CatchUp; + self.below_exit_threshold_since = None; + self.last_catch_up_exit_at = None; + true + } + + /// Applies exit hysteresis while in `CatchUp` mode. + /// + /// The policy requires queue pressure to stay below exit thresholds for the + /// full `EXIT_HOLD` window before returning to `Smooth`. + fn maybe_exit_catch_up(&mut self, snapshot: QueueSnapshot, now: Instant) { + if !should_exit_catch_up(snapshot) { + self.below_exit_threshold_since = None; + return; + } + + match self.below_exit_threshold_since { + Some(since) if now.saturating_duration_since(since) >= EXIT_HOLD => { + self.mode = ChunkingMode::Smooth; + self.below_exit_threshold_since = None; + self.last_catch_up_exit_at = Some(now); + } + Some(_) => {} + None => { + self.below_exit_threshold_since = Some(now); + } + } + } + + fn note_catch_up_exit(&mut self, now: Instant) { + if self.mode == ChunkingMode::CatchUp { + self.last_catch_up_exit_at = Some(now); + } + } + + fn reentry_hold_active(&self, now: Instant) -> bool { + self.last_catch_up_exit_at + .is_some_and(|exit| now.saturating_duration_since(exit) < REENTER_CATCH_UP_HOLD) + } +} + +/// Returns whether current queue pressure warrants entering catch-up mode. +/// +/// Either depth or age pressure is sufficient to trigger catch-up. +fn should_enter_catch_up(snapshot: QueueSnapshot) -> bool { + snapshot.queued_lines >= ENTER_QUEUE_DEPTH_LINES + || snapshot + .oldest_age + .is_some_and(|oldest| oldest >= ENTER_OLDEST_AGE) +} + +/// Returns whether queue pressure is low enough to begin exit hysteresis. +/// +/// Both depth and age must be below thresholds; this prevents oscillation when +/// one signal is still under load. +fn should_exit_catch_up(snapshot: QueueSnapshot) -> bool { + snapshot.queued_lines <= EXIT_QUEUE_DEPTH_LINES + && snapshot + .oldest_age + .is_some_and(|oldest| oldest <= EXIT_OLDEST_AGE) +} + +/// Returns whether backlog is severe enough to use a faster catch-up target. +/// +/// Severe pressure bypasses re-entry hold to avoid queue-age growth after a +/// recent catch-up exit. +fn is_severe_backlog(snapshot: QueueSnapshot) -> bool { + snapshot.queued_lines >= SEVERE_QUEUE_DEPTH_LINES + || snapshot + .oldest_age + .is_some_and(|oldest| oldest >= SEVERE_OLDEST_AGE) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + fn snapshot(queued_lines: usize, oldest_age_ms: u64) -> QueueSnapshot { + QueueSnapshot { + queued_lines, + oldest_age: Some(Duration::from_millis(oldest_age_ms)), + } + } + + #[test] + fn smooth_mode_is_default() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + + let decision = policy.decide(snapshot(1, 10), now); + assert_eq!(decision.mode, ChunkingMode::Smooth); + assert_eq!(decision.entered_catch_up, false); + assert_eq!(decision.drain_plan, DrainPlan::Single); + } + + #[test] + fn enters_catch_up_on_depth_threshold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + + let decision = policy.decide(snapshot(8, 10), now); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.entered_catch_up, true); + assert_eq!(decision.drain_plan, DrainPlan::Batch(8)); + } + + #[test] + fn enters_catch_up_on_age_threshold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + + let decision = policy.decide(snapshot(2, 120), now); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.entered_catch_up, true); + assert_eq!(decision.drain_plan, DrainPlan::Batch(2)); + } + + #[test] + fn severe_backlog_uses_faster_paced_batches() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + let _ = policy.decide(snapshot(9, 10), now); + + let decision = policy.decide(snapshot(64, 10), now + Duration::from_millis(5)); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.drain_plan, DrainPlan::Batch(64)); + } + + #[test] + fn catch_up_batch_drains_current_backlog() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + let decision = policy.decide(snapshot(512, 400), now); + assert_eq!(decision.mode, ChunkingMode::CatchUp); + assert_eq!(decision.drain_plan, DrainPlan::Batch(512)); + } + + #[test] + fn exits_catch_up_after_hysteresis_hold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let t0 = Instant::now(); + + let _ = policy.decide(snapshot(9, 10), t0); + assert_eq!(policy.mode(), ChunkingMode::CatchUp); + + let pre_hold = policy.decide(snapshot(2, 40), t0 + Duration::from_millis(200)); + assert_eq!(pre_hold.mode, ChunkingMode::CatchUp); + + let post_hold = policy.decide(snapshot(2, 40), t0 + Duration::from_millis(460)); + assert_eq!(post_hold.mode, ChunkingMode::Smooth); + assert_eq!(post_hold.drain_plan, DrainPlan::Single); + } + + #[test] + fn drops_back_to_smooth_when_idle() { + let mut policy = AdaptiveChunkingPolicy::default(); + let now = Instant::now(); + let _ = policy.decide(snapshot(9, 10), now); + assert_eq!(policy.mode(), ChunkingMode::CatchUp); + + let decision = policy.decide( + QueueSnapshot { + queued_lines: 0, + oldest_age: None, + }, + now + Duration::from_millis(20), + ); + assert_eq!(decision.mode, ChunkingMode::Smooth); + assert_eq!(decision.drain_plan, DrainPlan::Single); + } + + #[test] + fn holds_reentry_after_catch_up_exit() { + let mut policy = AdaptiveChunkingPolicy::default(); + let t0 = Instant::now(); + + let entered = policy.decide(snapshot(8, 20), t0); + assert_eq!(entered.mode, ChunkingMode::CatchUp); + + let drained = policy.decide( + QueueSnapshot { + queued_lines: 0, + oldest_age: None, + }, + t0 + Duration::from_millis(20), + ); + assert_eq!(drained.mode, ChunkingMode::Smooth); + + let held = policy.decide(snapshot(8, 20), t0 + Duration::from_millis(120)); + assert_eq!(held.mode, ChunkingMode::Smooth); + assert_eq!(held.drain_plan, DrainPlan::Single); + + let reentered = policy.decide(snapshot(8, 20), t0 + Duration::from_millis(320)); + assert_eq!(reentered.mode, ChunkingMode::CatchUp); + assert_eq!(reentered.drain_plan, DrainPlan::Batch(8)); + } + + #[test] + fn severe_backlog_can_reenter_during_hold() { + let mut policy = AdaptiveChunkingPolicy::default(); + let t0 = Instant::now(); + + let _ = policy.decide(snapshot(8, 20), t0); + let _ = policy.decide( + QueueSnapshot { + queued_lines: 0, + oldest_age: None, + }, + t0 + Duration::from_millis(20), + ); + + let severe = policy.decide(snapshot(64, 20), t0 + Duration::from_millis(120)); + assert_eq!(severe.mode, ChunkingMode::CatchUp); + assert_eq!(severe.drain_plan, DrainPlan::Batch(64)); + } +} diff --git a/codex-rs/tui/src/streaming/commit_tick.rs b/codex-rs/tui/src/streaming/commit_tick.rs new file mode 100644 index 0000000000..bda63bccf6 --- /dev/null +++ b/codex-rs/tui/src/streaming/commit_tick.rs @@ -0,0 +1,224 @@ +//! Orchestrates commit-tick drains across streaming controllers. +//! +//! This module bridges queue-based chunking policy (`chunking`) with the concrete stream +//! controllers (`controller`). Callers provide the current controllers and tick scope; the module +//! computes queue pressure, selects a drain plan, applies it, and returns emitted history cells. +//! +//! The module preserves ordering by draining only from controller queue heads. It does not schedule +//! ticks and it does not mutate UI state directly; callers remain responsible for animation events +//! and history insertion side effects. +//! +//! The main flow is: +//! [`run_commit_tick`] -> [`stream_queue_snapshot`] -> [`QueueSnapshot`] -> +//! [`resolve_chunking_plan`] -> [`ChunkingDecision`]/[`DrainPlan`] -> +//! [`apply_commit_tick_plan`] -> [`CommitTickOutput`]. + +use std::time::Duration; +use std::time::Instant; + +use crate::history_cell::HistoryCell; + +use super::chunking::AdaptiveChunkingPolicy; +use super::chunking::ChunkingDecision; +use super::chunking::ChunkingMode; +use super::chunking::DrainPlan; +use super::chunking::QueueSnapshot; +use super::controller::PlanStreamController; +use super::controller::StreamController; + +/// Describes whether a commit tick may run in all modes or only in catch-up mode. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum CommitTickScope { + /// Always run the tick, regardless of current chunking mode. + AnyMode, + /// Run model transitions and policy updates, but commit lines only in `CatchUp`. + CatchUpOnly, +} + +/// Describes what a single commit tick produced. +pub(crate) struct CommitTickOutput { + /// Cells produced by drained stream lines during this tick. + pub(crate) cells: Vec>, + /// Whether at least one stream controller was present for this tick. + pub(crate) has_controller: bool, + /// Whether all present controllers were idle after this tick. + pub(crate) all_idle: bool, +} + +impl Default for CommitTickOutput { + /// Creates an output that represents "no commit performed". + /// + /// This is used when a tick is intentionally suppressed, for example when + /// the scope is [`CommitTickScope::CatchUpOnly`] and policy is not in catch-up mode. + fn default() -> Self { + Self { + cells: Vec::new(), + has_controller: false, + all_idle: true, + } + } +} + +/// Runs one commit tick against the provided stream controllers. +/// +/// This function collects a [`QueueSnapshot`], asks [`AdaptiveChunkingPolicy`] for a +/// [`ChunkingDecision`], and then applies the resulting [`DrainPlan`] to both controllers. +/// If callers pass stale controller references (for example, references not tied to the +/// current turn), queue age can be misread and the policy may stay in catch-up longer +/// than expected. +pub(crate) fn run_commit_tick( + policy: &mut AdaptiveChunkingPolicy, + stream_controller: Option<&mut StreamController>, + plan_stream_controller: Option<&mut PlanStreamController>, + scope: CommitTickScope, + now: Instant, +) -> CommitTickOutput { + let snapshot = stream_queue_snapshot( + stream_controller.as_deref(), + plan_stream_controller.as_deref(), + now, + ); + let decision = resolve_chunking_plan(policy, snapshot, now); + if scope == CommitTickScope::CatchUpOnly && decision.mode != ChunkingMode::CatchUp { + return CommitTickOutput::default(); + } + + let output = apply_commit_tick_plan( + decision.drain_plan, + stream_controller, + plan_stream_controller, + ); + tracing::trace!( + mode = ?decision.mode, + queued_lines = snapshot.queued_lines, + oldest_queued_age_ms = snapshot.oldest_age.map(|age| age.as_millis() as u64), + drain_plan = ?decision.drain_plan, + has_controller = output.has_controller, + all_idle = output.all_idle, + "stream chunking commit tick" + ); + output +} + +/// Builds the combined queue-pressure snapshot consumed by chunking policy. +/// +/// The snapshot sums queue depth across controllers and keeps the maximum oldest age +/// so policy decisions reflect the most delayed queued line currently visible. +fn stream_queue_snapshot( + stream_controller: Option<&StreamController>, + plan_stream_controller: Option<&PlanStreamController>, + now: Instant, +) -> QueueSnapshot { + let mut queued_lines = 0usize; + let mut oldest_age: Option = None; + + if let Some(controller) = stream_controller { + queued_lines += controller.queued_lines(); + oldest_age = max_duration(oldest_age, controller.oldest_queued_age(now)); + } + if let Some(controller) = plan_stream_controller { + queued_lines += controller.queued_lines(); + oldest_age = max_duration(oldest_age, controller.oldest_queued_age(now)); + } + + QueueSnapshot { + queued_lines, + oldest_age, + } +} + +/// Computes one policy decision and emits a trace log on mode transitions. +/// +/// This keeps policy transition logging in one place so callers can rely on +/// [`run_commit_tick`] to provide consistent observability. +fn resolve_chunking_plan( + policy: &mut AdaptiveChunkingPolicy, + snapshot: QueueSnapshot, + now: Instant, +) -> ChunkingDecision { + let prior_mode = policy.mode(); + let decision = policy.decide(snapshot, now); + if decision.mode != prior_mode { + tracing::trace!( + prior_mode = ?prior_mode, + new_mode = ?decision.mode, + queued_lines = snapshot.queued_lines, + oldest_queued_age_ms = snapshot.oldest_age.map(|age| age.as_millis() as u64), + entered_catch_up = decision.entered_catch_up, + "stream chunking mode transition" + ); + } + decision +} + +/// Applies a [`DrainPlan`] to all available stream controllers. +/// +/// The returned [`CommitTickOutput`] reports emitted cells and whether all +/// present controllers are idle after draining. +fn apply_commit_tick_plan( + drain_plan: DrainPlan, + stream_controller: Option<&mut StreamController>, + plan_stream_controller: Option<&mut PlanStreamController>, +) -> CommitTickOutput { + let mut output = CommitTickOutput::default(); + + if let Some(controller) = stream_controller { + output.has_controller = true; + let (cell, is_idle) = drain_stream_controller(controller, drain_plan); + if let Some(cell) = cell { + output.cells.push(cell); + } + output.all_idle &= is_idle; + } + if let Some(controller) = plan_stream_controller { + output.has_controller = true; + let (cell, is_idle) = drain_plan_stream_controller(controller, drain_plan); + if let Some(cell) = cell { + output.cells.push(cell); + } + output.all_idle &= is_idle; + } + + output +} + +/// Applies one drain step to the main stream controller. +/// +/// [`DrainPlan::Single`] maps to one-line drain; [`DrainPlan::Batch`] maps to +/// multi-line drain (including instant catch-up when policy requests the full +/// queued backlog). +fn drain_stream_controller( + controller: &mut StreamController, + drain_plan: DrainPlan, +) -> (Option>, bool) { + match drain_plan { + DrainPlan::Single => controller.on_commit_tick(), + DrainPlan::Batch(max_lines) => controller.on_commit_tick_batch(max_lines), + } +} + +/// Applies one drain step to the plan stream controller. +/// +/// This mirrors [`drain_stream_controller`] so both controller types follow the +/// same chunking policy decisions. +fn drain_plan_stream_controller( + controller: &mut PlanStreamController, + drain_plan: DrainPlan, +) -> (Option>, bool) { + match drain_plan { + DrainPlan::Single => controller.on_commit_tick(), + DrainPlan::Batch(max_lines) => controller.on_commit_tick_batch(max_lines), + } +} + +/// Returns the greater of two optional durations. +/// +/// This helper preserves whichever side is present when only one duration exists. +fn max_duration(lhs: Option, rhs: Option) -> Option { + match (lhs, rhs) { + (Some(left), Some(right)) => Some(left.max(right)), + (Some(left), None) => Some(left), + (None, Some(right)) => Some(right), + (None, None) => None, + } +} diff --git a/codex-rs/tui/src/streaming/controller.rs b/codex-rs/tui/src/streaming/controller.rs index 462962e980..6117485adf 100644 --- a/codex-rs/tui/src/streaming/controller.rs +++ b/codex-rs/tui/src/streaming/controller.rs @@ -4,6 +4,8 @@ use crate::render::line_utils::prefix_lines; use crate::style::proposed_plan_style; use ratatui::prelude::Stylize; use ratatui::text::Line; +use std::time::Duration; +use std::time::Instant; use super::StreamState; @@ -71,6 +73,28 @@ impl StreamController { (self.emit(step), self.state.is_idle()) } + /// Step animation: commit at most `max_lines` queued lines. + /// + /// This is intended for adaptive catch-up drains. Callers should keep `max_lines` bounded; a + /// very large value can collapse perceived animation into a single jump. + pub(crate) fn on_commit_tick_batch( + &mut self, + max_lines: usize, + ) -> (Option>, bool) { + let step = self.state.drain_n(max_lines.max(1)); + (self.emit(step), self.state.is_idle()) + } + + /// Returns the current number of queued lines waiting to be displayed. + pub(crate) fn queued_lines(&self) -> usize { + self.state.queued_len() + } + + /// Returns the age of the oldest queued line. + pub(crate) fn oldest_queued_age(&self, now: Instant) -> Option { + self.state.oldest_queued_age(now) + } + fn emit(&mut self, lines: Vec>) -> Option> { if lines.is_empty() { return None; @@ -142,6 +166,28 @@ impl PlanStreamController { (self.emit(step, false), self.state.is_idle()) } + /// Step animation: commit at most `max_lines` queued lines. + /// + /// This is intended for adaptive catch-up drains. Callers should keep `max_lines` bounded; a + /// very large value can collapse perceived animation into a single jump. + pub(crate) fn on_commit_tick_batch( + &mut self, + max_lines: usize, + ) -> (Option>, bool) { + let step = self.state.drain_n(max_lines.max(1)); + (self.emit(step, false), self.state.is_idle()) + } + + /// Returns the current number of queued plan lines waiting to be displayed. + pub(crate) fn queued_lines(&self) -> usize { + self.state.queued_len() + } + + /// Returns the age of the oldest queued plan line. + pub(crate) fn oldest_queued_age(&self, now: Instant) -> Option { + self.state.oldest_queued_age(now) + } + fn emit( &mut self, lines: Vec>, diff --git a/codex-rs/tui/src/streaming/mod.rs b/codex-rs/tui/src/streaming/mod.rs index fa00702834..c783f27ae9 100644 --- a/codex-rs/tui/src/streaming/mod.rs +++ b/codex-rs/tui/src/streaming/mod.rs @@ -1,17 +1,39 @@ +//! Streaming primitives used by the TUI transcript pipeline. +//! +//! `StreamState` owns newline-gated markdown collection and a FIFO queue of committed render lines. +//! Higher-level modules build on top of this state: +//! - `controller` adapts queued lines into `HistoryCell` emission rules for message and plan streams. +//! - `chunking` computes adaptive drain plans from queue pressure. +//! - `commit_tick` binds policy decisions to concrete controller drains. +//! +//! The key invariant is queue ordering. All drains pop from the front, and enqueue records an +//! arrival timestamp so policy code can reason about oldest queued age without peeking into text. + use std::collections::VecDeque; +use std::time::Duration; +use std::time::Instant; use ratatui::text::Line; use crate::markdown_stream::MarkdownStreamCollector; +pub(crate) mod chunking; +pub(crate) mod commit_tick; pub(crate) mod controller; +struct QueuedLine { + line: Line<'static>, + enqueued_at: Instant, +} + +/// Holds in-flight markdown stream state and queued committed lines. pub(crate) struct StreamState { pub(crate) collector: MarkdownStreamCollector, - queued_lines: VecDeque>, + queued_lines: VecDeque, pub(crate) has_seen_delta: bool, } impl StreamState { + /// Creates an empty stream state with an optional target wrap width. pub(crate) fn new(width: Option) -> Self { Self { collector: MarkdownStreamCollector::new(width), @@ -19,21 +41,75 @@ impl StreamState { has_seen_delta: false, } } + /// Resets collector and queue state for the next stream lifecycle. pub(crate) fn clear(&mut self) { self.collector.clear(); self.queued_lines.clear(); self.has_seen_delta = false; } + /// Drains one queued line from the front of the queue. pub(crate) fn step(&mut self) -> Vec> { - self.queued_lines.pop_front().into_iter().collect() + self.queued_lines + .pop_front() + .map(|queued| queued.line) + .into_iter() + .collect() } + /// Drains up to `max_lines` queued lines from the front of the queue. + /// + /// Callers that pass very large values still get bounded behavior because this method clamps to + /// the currently available queue length. + pub(crate) fn drain_n(&mut self, max_lines: usize) -> Vec> { + let end = max_lines.min(self.queued_lines.len()); + self.queued_lines + .drain(..end) + .map(|queued| queued.line) + .collect() + } + /// Drains all queued lines from the front of the queue. pub(crate) fn drain_all(&mut self) -> Vec> { - self.queued_lines.drain(..).collect() + self.queued_lines + .drain(..) + .map(|queued| queued.line) + .collect() } + /// Returns whether no lines are queued for commit. pub(crate) fn is_idle(&self) -> bool { self.queued_lines.is_empty() } + /// Returns the current queue depth. + pub(crate) fn queued_len(&self) -> usize { + self.queued_lines.len() + } + /// Returns the age of the oldest queued line. + pub(crate) fn oldest_queued_age(&self, now: Instant) -> Option { + self.queued_lines + .front() + .map(|queued| now.saturating_duration_since(queued.enqueued_at)) + } + /// Appends committed lines to the queue with a shared enqueue timestamp. pub(crate) fn enqueue(&mut self, lines: Vec>) { - self.queued_lines.extend(lines); + let now = Instant::now(); + self.queued_lines + .extend(lines.into_iter().map(|line| QueuedLine { + line, + enqueued_at: now, + })); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn drain_n_clamps_to_available_lines() { + let mut state = StreamState::new(None); + state.enqueue(vec![Line::from("one")]); + + let drained = state.drain_n(8); + assert_eq!(drained, vec![Line::from("one")]); + assert!(state.is_idle()); } } diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index 761fa83627..a77785c1b8 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -10,6 +10,7 @@ use std::pin::Pin; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; +use std::time::Duration; use crossterm::Command; use crossterm::SynchronizedUpdate; @@ -52,6 +53,9 @@ mod frame_requester; #[cfg(unix)] mod job_control; +/// Target frame interval for UI redraw scheduling. +pub(crate) const TARGET_FRAME_INTERVAL: Duration = frame_rate_limiter::MIN_FRAME_INTERVAL; + /// A type alias for the terminal type used in this application pub type Terminal = CustomTerminal>; diff --git a/codex-rs/tui/src/tui/frame_rate_limiter.rs b/codex-rs/tui/src/tui/frame_rate_limiter.rs index 56dd752e64..6f4ad8e747 100644 --- a/codex-rs/tui/src/tui/frame_rate_limiter.rs +++ b/codex-rs/tui/src/tui/frame_rate_limiter.rs @@ -1,7 +1,7 @@ //! Limits how frequently frame draw notifications may be emitted. //! //! Widgets sometimes call `FrameRequester::schedule_frame()` more frequently than a user can -//! perceive. This limiter clamps draw notifications to a maximum of 60 FPS to avoid wasted work. +//! perceive. This limiter clamps draw notifications to a maximum of 120 FPS to avoid wasted work. //! //! This is intentionally a small, pure helper so it can be unit-tested in isolation and used by //! the async frame scheduler without adding complexity to the app/event loop. @@ -9,8 +9,8 @@ use std::time::Duration; use std::time::Instant; -/// A 60 FPS minimum frame interval (≈16.67ms). -pub(super) const MIN_FRAME_INTERVAL: Duration = Duration::from_nanos(16_666_667); +/// A 120 FPS minimum frame interval (≈8.33ms). +pub(super) const MIN_FRAME_INTERVAL: Duration = Duration::from_nanos(8_333_334); /// Remembers the most recent emitted draw, allowing deadlines to be clamped forward. #[derive(Debug, Default)] diff --git a/codex-rs/tui/src/tui/frame_requester.rs b/codex-rs/tui/src/tui/frame_requester.rs index 88db9d71f3..d7e54d82cc 100644 --- a/codex-rs/tui/src/tui/frame_requester.rs +++ b/codex-rs/tui/src/tui/frame_requester.rs @@ -71,7 +71,7 @@ impl FrameRequester { /// /// This type is internal to `FrameRequester` and is spawned as a task to handle scheduling logic. /// -/// To avoid wasted redraw work, draw notifications are clamped to a maximum of 60 FPS (see +/// To avoid wasted redraw work, draw notifications are clamped to a maximum of 120 FPS (see /// [`FrameRateLimiter`]). struct FrameScheduler { receiver: mpsc::UnboundedReceiver, @@ -232,7 +232,7 @@ mod tests { } #[tokio::test(flavor = "current_thread", start_paused = true)] - async fn test_limits_draw_notifications_to_60fps() { + async fn test_limits_draw_notifications_to_120fps() { let (draw_tx, mut draw_rx) = broadcast::channel(16); let requester = FrameRequester::new(draw_tx); @@ -250,7 +250,7 @@ mod tests { let early = draw_rx.recv().timeout(Duration::from_millis(1)).await; assert!( early.is_err(), - "draw fired too early; expected max 60fps (min interval {MIN_FRAME_INTERVAL:?})" + "draw fired too early; expected max 120fps (min interval {MIN_FRAME_INTERVAL:?})" ); time::advance(MIN_FRAME_INTERVAL).await; @@ -278,11 +278,11 @@ mod tests { requester.schedule_frame_in(Duration::from_millis(1)); - time::advance(Duration::from_millis(10)).await; + time::advance(MIN_FRAME_INTERVAL / 2).await; let too_early = draw_rx.recv().timeout(Duration::from_millis(1)).await; assert!( too_early.is_err(), - "draw fired too early; expected max 60fps (min interval {MIN_FRAME_INTERVAL:?})" + "draw fired too early; expected max 120fps (min interval {MIN_FRAME_INTERVAL:?})" ); time::advance(MIN_FRAME_INTERVAL).await; diff --git a/docs/tui-stream-chunking-review.md b/docs/tui-stream-chunking-review.md new file mode 100644 index 0000000000..3722492ddf --- /dev/null +++ b/docs/tui-stream-chunking-review.md @@ -0,0 +1,124 @@ +# TUI Stream Chunking + +This document explains how stream chunking in the TUI works and why it is +implemented this way. + +## Problem + +Streaming output can arrive faster than a one-line-per-tick animation can show +it. If commit speed stays fixed while arrival speed spikes, queued lines grow +and visible output lags behind received output. + +## Design goals + +- Preserve existing baseline behavior under normal load. +- Reduce display lag when backlog builds. +- Keep output order stable. +- Avoid abrupt single-frame flushes that look jumpy. +- Keep policy transport-agnostic and based only on queue state. + +## Non-goals + +- The policy does not schedule animation ticks. +- The policy does not depend on upstream source identity. +- The policy does not reorder queued output. + +## Where the logic lives + +- `codex-rs/tui/src/streaming/chunking.rs` + - Adaptive policy, mode transitions, and drain-plan selection. +- `codex-rs/tui/src/streaming/commit_tick.rs` + - Orchestration for each commit tick: snapshot, decide, drain, trace. +- `codex-rs/tui/src/streaming/controller.rs` + - Queue/drain primitives used by commit-tick orchestration. +- `codex-rs/tui/src/chatwidget.rs` + - Integration point that invokes commit-tick orchestration and handles UI + lifecycle events. + +## Runtime flow + +On each commit tick: + +1. Build a queue snapshot across active controllers. + - `queued_lines`: total queued lines. + - `oldest_age`: max age of the oldest queued line across controllers. +2. Ask adaptive policy for a decision. + - Output: current mode and a drain plan. +3. Apply drain plan to each controller. +4. Emit drained `HistoryCell`s for insertion by the caller. +5. Emit trace logs for observability. + +In `CatchUpOnly` scope, policy state still advances, but draining is skipped +unless mode is currently `CatchUp`. + +## Modes and transitions + +Two modes are used: + +- `Smooth` + - Baseline behavior: one line drained per baseline commit tick. + - Baseline tick interval currently comes from + `tui/src/app.rs:COMMIT_ANIMATION_TICK` (~8.3ms, ~120fps). +- `CatchUp` + - Drain current queued backlog per tick via `Batch(queued_lines)`. + +Entry and exit use hysteresis: + +- Enter `CatchUp` when queue depth or queue age exceeds enter thresholds. +- Exit requires both depth and age to be below exit thresholds for a hold + window (`EXIT_HOLD`). + +This prevents oscillation when load hovers near thresholds. + +## Current experimental tuning values + +These are the current values in `streaming/chunking.rs` plus the baseline +commit tick in `tui/src/app.rs`. They are +experimental and may change as we gather more trace data. + +- Baseline commit tick: `~8.3ms` (`COMMIT_ANIMATION_TICK` in `app.rs`) +- Enter catch-up: + - `queued_lines >= 8` OR `oldest_age >= 120ms` +- Exit catch-up eligibility: + - `queued_lines <= 2` AND `oldest_age <= 40ms` +- Exit hold (`CatchUp -> Smooth`): `250ms` +- Re-entry hold after catch-up exit: `250ms` +- Severe backlog thresholds: + - `queued_lines >= 64` OR `oldest_age >= 300ms` + +## Drain planning + +In `Smooth`, plan is always `Single`. + +In `CatchUp`, plan is `Batch(queued_lines)`, which drains the currently queued +backlog for immediate convergence. + +## Why this design + +This keeps normal animation semantics intact, while making backlog behavior +adaptive: + +- Under normal load, behavior stays familiar and stable. +- Under pressure, queue age is reduced quickly without sacrificing ordering. +- Hysteresis avoids rapid mode flapping. + +## Invariants + +- Queue order is preserved. +- Empty queue resets policy back to `Smooth`. +- `CatchUp` exits only after sustained low pressure. +- Catch-up drains are immediate while in `CatchUp`. + +## Observability + +Trace events are emitted from commit-tick orchestration: + +- `stream chunking commit tick` + - `mode`, `queued_lines`, `oldest_queued_age_ms`, `drain_plan`, + `has_controller`, `all_idle` +- `stream chunking mode transition` + - `prior_mode`, `new_mode`, `queued_lines`, `oldest_queued_age_ms`, + `entered_catch_up` + +These events are intended to explain display lag by showing queue pressure, +selected drain behavior, and mode transitions over time. diff --git a/docs/tui-stream-chunking-tuning.md b/docs/tui-stream-chunking-tuning.md new file mode 100644 index 0000000000..d9a2ea5e21 --- /dev/null +++ b/docs/tui-stream-chunking-tuning.md @@ -0,0 +1,98 @@ +# TUI Stream Chunking Tuning Guide + +This document explains how to tune adaptive stream chunking constants without +changing the underlying policy shape. + +## Scope + +Use this guide when adjusting queue-pressure thresholds and hysteresis windows in +`codex-rs/tui/src/streaming/chunking.rs`, and baseline commit cadence in +`codex-rs/tui/src/app.rs`. + +This guide is about tuning behavior, not redesigning the policy. + +## Before tuning + +- Keep the baseline behavior intact: + - `Smooth` mode drains one line per baseline tick. + - `CatchUp` mode drains queued backlog immediately. +- Capture trace logs with: + - `codex_tui::streaming::commit_tick` +- Evaluate on sustained, bursty, and mixed-output prompts. + +See `docs/tui-stream-chunking-validation.md` for the measurement process. + +## Tuning goals + +Tune for all three goals together: + +- low visible lag under bursty output +- low mode flapping (`Smooth <-> CatchUp` chatter) +- stable catch-up entry/exit behavior under mixed workloads + +## Constants and what they control + +### Baseline commit cadence + +- `COMMIT_ANIMATION_TICK` (`tui/src/app.rs`) + - Lower values increase smooth-mode update cadence and reduce steady-state lag. + - Higher values increase smoothing and can increase perceived lag. + - This should usually move after chunking thresholds/holds are in a good range. + +### Enter/exit thresholds + +- `ENTER_QUEUE_DEPTH_LINES`, `ENTER_OLDEST_AGE` + - Lower values enter catch-up earlier (less lag, more mode switching risk). + - Higher values enter later (more lag tolerance, fewer mode switches). +- `EXIT_QUEUE_DEPTH_LINES`, `EXIT_OLDEST_AGE` + - Lower values keep catch-up active longer. + - Higher values allow earlier exit and may increase re-entry churn. + +### Hysteresis holds + +- `EXIT_HOLD` + - Longer hold reduces flip-flop exits when pressure is noisy. + - Too long can keep catch-up active after pressure has cleared. +- `REENTER_CATCH_UP_HOLD` + - Longer hold suppresses rapid re-entry after exit. + - Too long can delay needed catch-up for near-term bursts. + - Severe backlog bypasses this hold by design. + +### Severe-backlog gates + +- `SEVERE_QUEUE_DEPTH_LINES`, `SEVERE_OLDEST_AGE` + - Lower values bypass re-entry hold earlier. + - Higher values reserve hold bypass for only extreme pressure. + +## Recommended tuning order + +Tune in this order to keep cause/effect clear: + +1. Entry/exit thresholds (`ENTER_*`, `EXIT_*`) +2. Hold windows (`EXIT_HOLD`, `REENTER_CATCH_UP_HOLD`) +3. Severe gates (`SEVERE_*`) +4. Baseline cadence (`COMMIT_ANIMATION_TICK`) + +Change one logical group at a time and re-measure before the next group. + +## Symptom-driven adjustments + +- Too much lag before catch-up starts: + - lower `ENTER_QUEUE_DEPTH_LINES` and/or `ENTER_OLDEST_AGE` +- Frequent `Smooth -> CatchUp -> Smooth` chatter: + - increase `EXIT_HOLD` + - increase `REENTER_CATCH_UP_HOLD` + - tighten exit thresholds (lower `EXIT_*`) +- Catch-up engages too often for short bursts: + - increase `ENTER_QUEUE_DEPTH_LINES` and/or `ENTER_OLDEST_AGE` + - increase `REENTER_CATCH_UP_HOLD` +- Catch-up engages too late: + - lower `ENTER_QUEUE_DEPTH_LINES` and/or `ENTER_OLDEST_AGE` + - lower severe gates (`SEVERE_*`) to bypass re-entry hold sooner + +## Validation checklist after each tuning pass + +- `cargo test -p codex-tui` passes. +- Trace window shows bounded queue-age behavior. +- Mode transitions are not concentrated in repeated short-interval cycles. +- Catch-up clears backlog quickly once mode enters `CatchUp`. diff --git a/docs/tui-stream-chunking-validation.md b/docs/tui-stream-chunking-validation.md new file mode 100644 index 0000000000..26bd949da6 --- /dev/null +++ b/docs/tui-stream-chunking-validation.md @@ -0,0 +1,105 @@ +# TUI Stream Chunking Validation Process + +This document records the process used to validate adaptive stream chunking +and anti-flap behavior. + +## Scope + +The goal is to verify two properties from runtime traces: + +- display lag is reduced when queue pressure rises +- mode transitions remain stable instead of rapidly flapping + +## Trace targets + +Chunking observability is emitted by: + +- `codex_tui::streaming::commit_tick` + +Two trace messages are used: + +- `stream chunking commit tick` +- `stream chunking mode transition` + +## Runtime command + +Run Codex with chunking traces enabled: + +```bash +RUST_LOG='codex_tui::streaming::commit_tick=trace,codex_tui=info,codex_core=info,codex_rmcp_client=info' \ + just codex --enable=responses_websockets +``` + +## Log capture process + +1. Record the current size of `~/.codex/log/codex-tui.log` as a start offset. +2. Run an interactive prompt that produces sustained streamed output. +3. Stop the run. +4. Parse only log bytes written after the recorded offset. + +This avoids mixing earlier sessions with the current measurement window. + +## Metrics reviewed + +For each measured window: + +- `commit_ticks` +- `mode_transitions` +- `smooth_ticks` +- `catchup_ticks` +- drain-plan distribution (`Single`, `Batch(n)`) +- queue depth (`max`, `p95`, `p99`) +- oldest queued age (`max`, `p95`, `p99`) +- rapid re-entry count: + - number of `Smooth -> CatchUp` transitions within 1 second of a + `CatchUp -> Smooth` transition + +## Interpretation + +- Healthy behavior: + - queue age remains bounded while backlog is drained + - transition count is low relative to total ticks + - rapid re-entry events are infrequent and localized to burst boundaries +- Regressed behavior: + - repeated short-interval mode toggles across an extended window + - persistent queue-age growth while in smooth mode + - long catch-up runs without backlog reduction + +## Experiment history + +This section captures the major tuning passes so future work can build on +what has already been tried. + +- Baseline + - One-line smooth draining with a 50ms commit tick. + - This preserved familiar pacing but could feel laggy under sustained + backlog. +- Pass 1: instant catch-up, baseline tick unchanged + - Kept smooth-mode semantics but made catch-up drain the full queued + backlog each catch-up tick. + - Result: queue lag dropped faster, but perceived motion could still feel + stepped because smooth-mode cadence remained coarse. +- Pass 2: faster baseline tick (25ms) + - Improved smooth-mode cadence and reduced visible stepping. + - Result: better, but still not aligned with draw cadence. +- Pass 3: frame-aligned baseline tick (~16.7ms) + - Set baseline commit cadence to approximately 60fps. + - Result: smoother perceived progression while retaining hysteresis and + fast backlog convergence. +- Pass 4: higher frame-aligned baseline tick (~8.3ms) + - Set baseline commit cadence to approximately 120fps. + - Result: further reduced smooth-mode stepping while preserving the same + adaptive catch-up policy shape. + +Current state combines: + +- instant catch-up draining in `CatchUp` +- hysteresis for mode-entry/exit stability +- frame-aligned smooth-mode commit cadence (~8.3ms) + +## Notes + +- Validation is source-agnostic and does not rely on naming any specific + upstream provider. +- This process intentionally preserves existing baseline smooth behavior and + focuses on burst/backlog handling behavior. From 16647b188b3a8988b1d27d1bd517854b4762bf19 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Tue, 3 Feb 2026 15:17:34 -0800 Subject: [PATCH 04/90] chore: add `codex debug app-server` tooling (#10367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit codex debug app-server forwards the message through codex-app-server-test-client’s send_message_v2 library entry point, using std::env::current_exe() to resolve the codex binary. for how it looks like, see: ``` celia@com-92114 codex-rs % cargo build -p codex-cli && target/debug/codex debug app-server --help Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.34s Tooling: helps debug the app server Usage: codex debug app-server [OPTIONS] Commands: send-message-v2 help Print this message or the help of the given subcommand(s) ```` and ``` celia@com-92114 codex-rs % cargo build -p codex-cli && target/debug/codex debug app-server send-message-v2 "hello world" Compiling codex-cli v0.0.0 (/Users/celia/code/codex/codex-rs/cli) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.38s > { > "method": "initialize", > "id": "f8ba9f60-3a49-4ea9-81d6-4ab6853e3954", > "params": { > "clientInfo": { > "name": "codex-toy-app-server", > "title": "Codex Toy App Server", > "version": "0.0.0" > }, > "capabilities": { > "experimentalApi": true > } > } > } < { < "id": "f8ba9f60-3a49-4ea9-81d6-4ab6853e3954", < "result": { < "userAgent": "codex-toy-app-server/0.0.0 (Mac OS 26.2.0; arm64) vscode/2.4.27 (codex-toy-app-server; 0.0.0)" < } < } < initialize response: InitializeResponse { user_agent: "codex-toy-app-server/0.0.0 (Mac OS 26.2.0; arm64) vscode/2.4.27 (codex-toy-app-server; 0.0.0)" } > { > "method": "thread/start", > "id": "203f1630-beee-4e60-b17b-9eff16b1638b", > "params": { > "model": null, > "modelProvider": null, > "cwd": null, > "approvalPolicy": null, > "sandbox": null, > "config": null, > "baseInstructions": null, > "developerInstructions": null, > "personality": null, > "ephemeral": null, > "dynamicTools": null, > "mockExperimentalField": null, > "experimentalRawEvents": false > } > } ... ``` --- codex-rs/Cargo.lock | 1 + codex-rs/Cargo.toml | 1 + codex-rs/app-server-test-client/BUILD.bazel | 2 +- codex-rs/app-server-test-client/src/lib.rs | 1057 ++++++++++++++++++ codex-rs/app-server-test-client/src/main.rs | 1058 +------------------ codex-rs/cli/Cargo.toml | 1 + codex-rs/cli/src/main.rs | 48 +- 7 files changed, 1109 insertions(+), 1059 deletions(-) create mode 100644 codex-rs/app-server-test-client/src/lib.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 3d9daeb7ed..43b4b295a8 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1379,6 +1379,7 @@ dependencies = [ "clap_complete", "codex-app-server", "codex-app-server-protocol", + "codex-app-server-test-client", "codex-arg0", "codex-chatgpt", "codex-cloud-tasks", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 2c92f0b463..39ebe5967d 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -70,6 +70,7 @@ codex-ansi-escape = { path = "ansi-escape" } codex-api = { path = "codex-api" } codex-app-server = { path = "app-server" } codex-app-server-protocol = { path = "app-server-protocol" } +codex-app-server-test-client = { path = "app-server-test-client" } codex-apply-patch = { path = "apply-patch" } codex-arg0 = { path = "arg0" } codex-async-utils = { path = "async-utils" } diff --git a/codex-rs/app-server-test-client/BUILD.bazel b/codex-rs/app-server-test-client/BUILD.bazel index e3610747cd..3a1686a04e 100644 --- a/codex-rs/app-server-test-client/BUILD.bazel +++ b/codex-rs/app-server-test-client/BUILD.bazel @@ -1,6 +1,6 @@ load("//:defs.bzl", "codex_rust_crate") codex_rust_crate( - name = "codex-app-server-test-client", + name = "app-server-test-client", crate_name = "codex_app_server_test_client", ) diff --git a/codex-rs/app-server-test-client/src/lib.rs b/codex-rs/app-server-test-client/src/lib.rs new file mode 100644 index 0000000000..90f6adf572 --- /dev/null +++ b/codex-rs/app-server-test-client/src/lib.rs @@ -0,0 +1,1057 @@ +use std::collections::VecDeque; +use std::fs; +use std::io::BufRead; +use std::io::BufReader; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; +use std::process::Child; +use std::process::ChildStdin; +use std::process::ChildStdout; +use std::process::Command; +use std::process::Stdio; +use std::thread; +use std::time::Duration; + +use anyhow::Context; +use anyhow::Result; +use anyhow::bail; +use clap::ArgAction; +use clap::Parser; +use clap::Subcommand; +use codex_app_server_protocol::AddConversationListenerParams; +use codex_app_server_protocol::AddConversationSubscriptionResponse; +use codex_app_server_protocol::AskForApproval; +use codex_app_server_protocol::ClientInfo; +use codex_app_server_protocol::ClientRequest; +use codex_app_server_protocol::CommandExecutionApprovalDecision; +use codex_app_server_protocol::CommandExecutionRequestApprovalParams; +use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; +use codex_app_server_protocol::DynamicToolSpec; +use codex_app_server_protocol::FileChangeApprovalDecision; +use codex_app_server_protocol::FileChangeRequestApprovalParams; +use codex_app_server_protocol::FileChangeRequestApprovalResponse; +use codex_app_server_protocol::GetAccountRateLimitsResponse; +use codex_app_server_protocol::InitializeCapabilities; +use codex_app_server_protocol::InitializeParams; +use codex_app_server_protocol::InitializeResponse; +use codex_app_server_protocol::InputItem; +use codex_app_server_protocol::JSONRPCMessage; +use codex_app_server_protocol::JSONRPCNotification; +use codex_app_server_protocol::JSONRPCRequest; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::LoginChatGptCompleteNotification; +use codex_app_server_protocol::LoginChatGptResponse; +use codex_app_server_protocol::ModelListParams; +use codex_app_server_protocol::ModelListResponse; +use codex_app_server_protocol::NewConversationParams; +use codex_app_server_protocol::NewConversationResponse; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::SandboxPolicy; +use codex_app_server_protocol::SendUserMessageParams; +use codex_app_server_protocol::SendUserMessageResponse; +use codex_app_server_protocol::ServerNotification; +use codex_app_server_protocol::ServerRequest; +use codex_app_server_protocol::ThreadStartParams; +use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::TurnStartParams; +use codex_app_server_protocol::TurnStartResponse; +use codex_app_server_protocol::TurnStatus; +use codex_app_server_protocol::UserInput as V2UserInput; +use codex_protocol::ThreadId; +use codex_protocol::protocol::Event; +use codex_protocol::protocol::EventMsg; +use serde::Serialize; +use serde::de::DeserializeOwned; +use serde_json::Value; +use uuid::Uuid; + +/// Minimal launcher that initializes the Codex app-server and logs the handshake. +#[derive(Parser)] +#[command(author = "Codex", version, about = "Bootstrap Codex app-server", long_about = None)] +struct Cli { + /// Path to the `codex` CLI binary. + #[arg(long, env = "CODEX_BIN", default_value = "codex")] + codex_bin: PathBuf, + + /// Forwarded to the `codex` CLI as `--config key=value`. Repeatable. + /// + /// Example: + /// `--config 'model_providers.mock.base_url="http://localhost:4010/v2"'` + #[arg( + short = 'c', + long = "config", + value_name = "key=value", + action = ArgAction::Append, + global = true + )] + config_overrides: Vec, + + /// JSON array of dynamic tool specs or a single tool object. + /// Prefix a filename with '@' to read from a file. + /// + /// Example: + /// --dynamic-tools '[{"name":"demo","description":"Demo","inputSchema":{"type":"object"}}]' + /// --dynamic-tools @/path/to/tools.json + #[arg(long, value_name = "json-or-@file", global = true)] + dynamic_tools: Option, + + #[command(subcommand)] + command: CliCommand, +} + +#[derive(Subcommand)] +enum CliCommand { + /// Send a user message through the Codex app-server. + SendMessage { + /// User message to send to Codex. + user_message: String, + }, + /// Send a user message through the app-server V2 thread/turn APIs. + SendMessageV2 { + /// User message to send to Codex. + user_message: String, + }, + /// Start a V2 turn that elicits an ExecCommand approval. + #[command(name = "trigger-cmd-approval")] + TriggerCmdApproval { + /// Optional prompt; defaults to a simple python command. + user_message: Option, + }, + /// Start a V2 turn that elicits an ApplyPatch approval. + #[command(name = "trigger-patch-approval")] + TriggerPatchApproval { + /// Optional prompt; defaults to creating a file via apply_patch. + user_message: Option, + }, + /// Start a V2 turn that should not elicit an ExecCommand approval. + #[command(name = "no-trigger-cmd-approval")] + NoTriggerCmdApproval, + /// Send two sequential V2 turns in the same thread to test follow-up behavior. + SendFollowUpV2 { + /// Initial user message for the first turn. + first_message: String, + /// Follow-up user message for the second turn. + follow_up_message: String, + }, + /// Trigger the ChatGPT login flow and wait for completion. + TestLogin, + /// Fetch the current account rate limits from the Codex app-server. + GetAccountRateLimits, + /// List the available models from the Codex app-server. + #[command(name = "model-list")] + ModelList, +} + +pub fn run() -> Result<()> { + let Cli { + codex_bin, + config_overrides, + dynamic_tools, + command, + } = Cli::parse(); + + let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?; + + match command { + CliCommand::SendMessage { user_message } => { + ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?; + send_message(&codex_bin, &config_overrides, user_message) + } + CliCommand::SendMessageV2 { user_message } => { + send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::TriggerCmdApproval { user_message } => { + trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::TriggerPatchApproval { user_message } => { + trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) + } + CliCommand::NoTriggerCmdApproval => { + no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools) + } + CliCommand::SendFollowUpV2 { + first_message, + follow_up_message, + } => send_follow_up_v2( + &codex_bin, + &config_overrides, + first_message, + follow_up_message, + &dynamic_tools, + ), + CliCommand::TestLogin => { + ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; + test_login(&codex_bin, &config_overrides) + } + CliCommand::GetAccountRateLimits => { + ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; + get_account_rate_limits(&codex_bin, &config_overrides) + } + CliCommand::ModelList => { + ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?; + model_list(&codex_bin, &config_overrides) + } + } +} + +fn send_message(codex_bin: &Path, config_overrides: &[String], user_message: String) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let conversation = client.start_thread()?; + println!("< newConversation response: {conversation:?}"); + + let subscription = client.add_conversation_listener(&conversation.conversation_id)?; + println!("< addConversationListener response: {subscription:?}"); + + let send_response = client.send_user_message(&conversation.conversation_id, &user_message)?; + println!("< sendUserMessage response: {send_response:?}"); + + client.stream_conversation(&conversation.conversation_id)?; + + client.remove_thread_listener(subscription.subscription_id)?; + + Ok(()) +} + +pub fn send_message_v2( + codex_bin: &Path, + config_overrides: &[String], + user_message: String, + dynamic_tools: &Option>, +) -> Result<()> { + send_message_v2_with_policies( + codex_bin, + config_overrides, + user_message, + None, + None, + dynamic_tools, + ) +} + +fn trigger_cmd_approval( + codex_bin: &Path, + config_overrides: &[String], + user_message: Option, + dynamic_tools: &Option>, +) -> Result<()> { + let default_prompt = + "Run `touch /tmp/should-trigger-approval` so I can confirm the file exists."; + let message = user_message.unwrap_or_else(|| default_prompt.to_string()); + send_message_v2_with_policies( + codex_bin, + config_overrides, + message, + Some(AskForApproval::OnRequest), + Some(SandboxPolicy::ReadOnly), + dynamic_tools, + ) +} + +fn trigger_patch_approval( + codex_bin: &Path, + config_overrides: &[String], + user_message: Option, + dynamic_tools: &Option>, +) -> Result<()> { + let default_prompt = + "Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch."; + let message = user_message.unwrap_or_else(|| default_prompt.to_string()); + send_message_v2_with_policies( + codex_bin, + config_overrides, + message, + Some(AskForApproval::OnRequest), + Some(SandboxPolicy::ReadOnly), + dynamic_tools, + ) +} + +fn no_trigger_cmd_approval( + codex_bin: &Path, + config_overrides: &[String], + dynamic_tools: &Option>, +) -> Result<()> { + let prompt = "Run `touch should_not_trigger_approval.txt`"; + send_message_v2_with_policies( + codex_bin, + config_overrides, + prompt.to_string(), + None, + None, + dynamic_tools, + ) +} + +fn send_message_v2_with_policies( + codex_bin: &Path, + config_overrides: &[String], + user_message: String, + approval_policy: Option, + sandbox_policy: Option, + dynamic_tools: &Option>, +) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; + println!("< thread/start response: {thread_response:?}"); + let mut turn_params = TurnStartParams { + thread_id: thread_response.thread.id.clone(), + input: vec![V2UserInput::Text { + text: user_message, + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + ..Default::default() + }; + turn_params.approval_policy = approval_policy; + turn_params.sandbox_policy = sandbox_policy; + + let turn_response = client.turn_start(turn_params)?; + println!("< turn/start response: {turn_response:?}"); + + client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?; + + Ok(()) +} + +fn send_follow_up_v2( + codex_bin: &Path, + config_overrides: &[String], + first_message: String, + follow_up_message: String, + dynamic_tools: &Option>, +) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let thread_response = client.thread_start(ThreadStartParams { + dynamic_tools: dynamic_tools.clone(), + ..Default::default() + })?; + println!("< thread/start response: {thread_response:?}"); + + let first_turn_params = TurnStartParams { + thread_id: thread_response.thread.id.clone(), + input: vec![V2UserInput::Text { + text: first_message, + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + ..Default::default() + }; + let first_turn_response = client.turn_start(first_turn_params)?; + println!("< turn/start response (initial): {first_turn_response:?}"); + client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?; + + let follow_up_params = TurnStartParams { + thread_id: thread_response.thread.id.clone(), + input: vec![V2UserInput::Text { + text: follow_up_message, + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + ..Default::default() + }; + let follow_up_response = client.turn_start(follow_up_params)?; + println!("< turn/start response (follow-up): {follow_up_response:?}"); + client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?; + + Ok(()) +} + +fn test_login(codex_bin: &Path, config_overrides: &[String]) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let login_response = client.login_chat_gpt()?; + println!("< loginChatGpt response: {login_response:?}"); + println!( + "Open the following URL in your browser to continue:\n{}", + login_response.auth_url + ); + + let completion = client.wait_for_login_completion(&login_response.login_id)?; + println!("< loginChatGptComplete notification: {completion:?}"); + + if completion.success { + println!("Login succeeded."); + Ok(()) + } else { + bail!( + "login failed: {}", + completion + .error + .as_deref() + .unwrap_or("unknown error from loginChatGptComplete") + ); + } +} + +fn get_account_rate_limits(codex_bin: &Path, config_overrides: &[String]) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let response = client.get_account_rate_limits()?; + println!("< account/rateLimits/read response: {response:?}"); + + Ok(()) +} + +fn model_list(codex_bin: &Path, config_overrides: &[String]) -> Result<()> { + let mut client = CodexClient::spawn(codex_bin, config_overrides)?; + + let initialize = client.initialize()?; + println!("< initialize response: {initialize:?}"); + + let response = client.model_list(ModelListParams::default())?; + println!("< model/list response: {response:?}"); + + Ok(()) +} + +fn ensure_dynamic_tools_unused( + dynamic_tools: &Option>, + command: &str, +) -> Result<()> { + if dynamic_tools.is_some() { + bail!( + "dynamic tools are only supported for v2 thread/start; remove --dynamic-tools for {command} or use send-message-v2" + ); + } + Ok(()) +} + +fn parse_dynamic_tools_arg(dynamic_tools: &Option) -> Result>> { + let Some(raw_arg) = dynamic_tools.as_deref() else { + return Ok(None); + }; + + let raw_json = if let Some(path) = raw_arg.strip_prefix('@') { + fs::read_to_string(Path::new(path)) + .with_context(|| format!("read dynamic tools file {path}"))? + } else { + raw_arg.to_string() + }; + + let value: Value = serde_json::from_str(&raw_json).context("parse dynamic tools JSON")?; + let tools = match value { + Value::Array(_) => serde_json::from_value(value).context("decode dynamic tools array")?, + Value::Object(_) => vec![serde_json::from_value(value).context("decode dynamic tool")?], + _ => bail!("dynamic tools JSON must be an object or array"), + }; + + Ok(Some(tools)) +} + +struct CodexClient { + child: Child, + stdin: Option, + stdout: BufReader, + pending_notifications: VecDeque, +} + +impl CodexClient { + fn spawn(codex_bin: &Path, config_overrides: &[String]) -> Result { + let codex_bin_display = codex_bin.display(); + let mut cmd = Command::new(codex_bin); + for override_kv in config_overrides { + cmd.arg("--config").arg(override_kv); + } + let mut codex_app_server = cmd + .arg("app-server") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .with_context(|| format!("failed to start `{codex_bin_display}` app-server"))?; + + let stdin = codex_app_server + .stdin + .take() + .context("codex app-server stdin unavailable")?; + let stdout = codex_app_server + .stdout + .take() + .context("codex app-server stdout unavailable")?; + + Ok(Self { + child: codex_app_server, + stdin: Some(stdin), + stdout: BufReader::new(stdout), + pending_notifications: VecDeque::new(), + }) + } + + fn initialize(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::Initialize { + request_id: request_id.clone(), + params: InitializeParams { + client_info: ClientInfo { + name: "codex-toy-app-server".to_string(), + title: Some("Codex Toy App Server".to_string()), + version: env!("CARGO_PKG_VERSION").to_string(), + }, + capabilities: Some(InitializeCapabilities { + experimental_api: true, + }), + }, + }; + + self.send_request(request, request_id, "initialize") + } + + fn start_thread(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::NewConversation { + request_id: request_id.clone(), + params: NewConversationParams::default(), + }; + + self.send_request(request, request_id, "newConversation") + } + + fn add_conversation_listener( + &mut self, + conversation_id: &ThreadId, + ) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::AddConversationListener { + request_id: request_id.clone(), + params: AddConversationListenerParams { + conversation_id: *conversation_id, + experimental_raw_events: false, + }, + }; + + self.send_request(request, request_id, "addConversationListener") + } + + fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> { + let request_id = self.request_id(); + let request = ClientRequest::RemoveConversationListener { + request_id: request_id.clone(), + params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id }, + }; + + self.send_request::( + request, + request_id, + "removeConversationListener", + )?; + + Ok(()) + } + + fn send_user_message( + &mut self, + conversation_id: &ThreadId, + message: &str, + ) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::SendUserMessage { + request_id: request_id.clone(), + params: SendUserMessageParams { + conversation_id: *conversation_id, + items: vec![InputItem::Text { + text: message.to_string(), + // Test client sends plain text without UI element ranges. + text_elements: Vec::new(), + }], + }, + }; + + self.send_request(request, request_id, "sendUserMessage") + } + + fn thread_start(&mut self, params: ThreadStartParams) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::ThreadStart { + request_id: request_id.clone(), + params, + }; + + self.send_request(request, request_id, "thread/start") + } + + fn turn_start(&mut self, params: TurnStartParams) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::TurnStart { + request_id: request_id.clone(), + params, + }; + + self.send_request(request, request_id, "turn/start") + } + + fn login_chat_gpt(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::LoginChatGpt { + request_id: request_id.clone(), + params: None, + }; + + self.send_request(request, request_id, "loginChatGpt") + } + + fn get_account_rate_limits(&mut self) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::GetAccountRateLimits { + request_id: request_id.clone(), + params: None, + }; + + self.send_request(request, request_id, "account/rateLimits/read") + } + + fn model_list(&mut self, params: ModelListParams) -> Result { + let request_id = self.request_id(); + let request = ClientRequest::ModelList { + request_id: request_id.clone(), + params, + }; + + self.send_request(request, request_id, "model/list") + } + + fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> { + loop { + let notification = self.next_notification()?; + + if !notification.method.starts_with("codex/event/") { + continue; + } + + if let Some(event) = self.extract_event(notification, conversation_id)? { + match &event.msg { + EventMsg::AgentMessage(event) => { + println!("{}", event.message); + } + EventMsg::AgentMessageDelta(event) => { + print!("{}", event.delta); + std::io::stdout().flush().ok(); + } + EventMsg::TurnComplete(event) => { + println!("\n[task complete: {event:?}]"); + break; + } + EventMsg::TurnAborted(event) => { + println!("\n[turn aborted: {:?}]", event.reason); + break; + } + EventMsg::Error(event) => { + println!("[error] {event:?}"); + } + _ => { + println!("[UNKNOWN EVENT] {:?}", event.msg); + } + } + } + } + + Ok(()) + } + + fn wait_for_login_completion( + &mut self, + expected_login_id: &Uuid, + ) -> Result { + loop { + let notification = self.next_notification()?; + + if let Ok(server_notification) = ServerNotification::try_from(notification) { + match server_notification { + ServerNotification::LoginChatGptComplete(completion) => { + if &completion.login_id == expected_login_id { + return Ok(completion); + } + + println!( + "[ignoring loginChatGptComplete for unexpected login_id: {}]", + completion.login_id + ); + } + ServerNotification::AuthStatusChange(status) => { + println!("< authStatusChange notification: {status:?}"); + } + ServerNotification::AccountRateLimitsUpdated(snapshot) => { + println!("< accountRateLimitsUpdated notification: {snapshot:?}"); + } + ServerNotification::SessionConfigured(_) => { + // SessionConfigured notifications are unrelated to login; skip. + } + _ => {} + } + } + + // Not a server notification (likely a conversation event); keep waiting. + } + } + + fn stream_turn(&mut self, thread_id: &str, turn_id: &str) -> Result<()> { + loop { + let notification = self.next_notification()?; + + let Ok(server_notification) = ServerNotification::try_from(notification) else { + continue; + }; + + match server_notification { + ServerNotification::ThreadStarted(payload) => { + if payload.thread.id == thread_id { + println!("< thread/started notification: {:?}", payload.thread); + } + } + ServerNotification::TurnStarted(payload) => { + if payload.turn.id == turn_id { + println!("< turn/started notification: {:?}", payload.turn.status); + } + } + ServerNotification::AgentMessageDelta(delta) => { + print!("{}", delta.delta); + std::io::stdout().flush().ok(); + } + ServerNotification::CommandExecutionOutputDelta(delta) => { + print!("{}", delta.delta); + std::io::stdout().flush().ok(); + } + ServerNotification::TerminalInteraction(delta) => { + println!("[stdin sent: {}]", delta.stdin); + std::io::stdout().flush().ok(); + } + ServerNotification::ItemStarted(payload) => { + println!("\n< item started: {:?}", payload.item); + } + ServerNotification::ItemCompleted(payload) => { + println!("< item completed: {:?}", payload.item); + } + ServerNotification::TurnCompleted(payload) => { + if payload.turn.id == turn_id { + println!("\n< turn/completed notification: {:?}", payload.turn.status); + if payload.turn.status == TurnStatus::Failed + && let Some(error) = payload.turn.error + { + println!("[turn error] {}", error.message); + } + break; + } + } + ServerNotification::McpToolCallProgress(payload) => { + println!("< MCP tool progress: {}", payload.message); + } + _ => { + println!("[UNKNOWN SERVER NOTIFICATION] {server_notification:?}"); + } + } + } + + Ok(()) + } + + fn extract_event( + &self, + notification: JSONRPCNotification, + conversation_id: &ThreadId, + ) -> Result> { + let params = notification + .params + .context("event notification missing params")?; + + let mut map = match params { + Value::Object(map) => map, + other => bail!("unexpected params shape: {other:?}"), + }; + + let conversation_value = map + .remove("conversationId") + .context("event missing conversationId")?; + let notification_conversation: ThreadId = serde_json::from_value(conversation_value) + .context("conversationId was not a valid UUID")?; + + if ¬ification_conversation != conversation_id { + return Ok(None); + } + + let event_value = Value::Object(map); + let event: Event = + serde_json::from_value(event_value).context("failed to decode event payload")?; + Ok(Some(event)) + } + + fn send_request( + &mut self, + request: ClientRequest, + request_id: RequestId, + method: &str, + ) -> Result + where + T: DeserializeOwned, + { + self.write_request(&request)?; + self.wait_for_response(request_id, method) + } + + fn write_request(&mut self, request: &ClientRequest) -> Result<()> { + let request_json = serde_json::to_string(request)?; + let request_pretty = serde_json::to_string_pretty(request)?; + print_multiline_with_prefix("> ", &request_pretty); + + if let Some(stdin) = self.stdin.as_mut() { + writeln!(stdin, "{request_json}")?; + stdin + .flush() + .context("failed to flush request to codex app-server")?; + } else { + bail!("codex app-server stdin closed"); + } + + Ok(()) + } + + fn wait_for_response(&mut self, request_id: RequestId, method: &str) -> Result + where + T: DeserializeOwned, + { + loop { + let message = self.read_jsonrpc_message()?; + + match message { + JSONRPCMessage::Response(JSONRPCResponse { id, result }) => { + if id == request_id { + return serde_json::from_value(result) + .with_context(|| format!("{method} response missing payload")); + } + } + JSONRPCMessage::Error(err) => { + if err.id == request_id { + bail!("{method} failed: {err:?}"); + } + } + JSONRPCMessage::Notification(notification) => { + self.pending_notifications.push_back(notification); + } + JSONRPCMessage::Request(request) => { + self.handle_server_request(request)?; + } + } + } + } + + fn next_notification(&mut self) -> Result { + if let Some(notification) = self.pending_notifications.pop_front() { + return Ok(notification); + } + + loop { + let message = self.read_jsonrpc_message()?; + + match message { + JSONRPCMessage::Notification(notification) => return Ok(notification), + JSONRPCMessage::Response(_) | JSONRPCMessage::Error(_) => { + // No outstanding requests, so ignore stray responses/errors for now. + continue; + } + JSONRPCMessage::Request(request) => { + self.handle_server_request(request)?; + } + } + } + } + + fn read_jsonrpc_message(&mut self) -> Result { + loop { + let mut response_line = String::new(); + let bytes = self + .stdout + .read_line(&mut response_line) + .context("failed to read from codex app-server")?; + + if bytes == 0 { + bail!("codex app-server closed stdout"); + } + + let trimmed = response_line.trim(); + if trimmed.is_empty() { + continue; + } + + let parsed: Value = + serde_json::from_str(trimmed).context("response was not valid JSON-RPC")?; + let pretty = serde_json::to_string_pretty(&parsed)?; + print_multiline_with_prefix("< ", &pretty); + let message: JSONRPCMessage = serde_json::from_value(parsed) + .context("response was not a valid JSON-RPC message")?; + return Ok(message); + } + } + + fn request_id(&self) -> RequestId { + RequestId::String(Uuid::new_v4().to_string()) + } + + fn handle_server_request(&mut self, request: JSONRPCRequest) -> Result<()> { + let server_request = ServerRequest::try_from(request) + .context("failed to deserialize ServerRequest from JSONRPCRequest")?; + + match server_request { + ServerRequest::CommandExecutionRequestApproval { request_id, params } => { + self.handle_command_execution_request_approval(request_id, params)?; + } + ServerRequest::FileChangeRequestApproval { request_id, params } => { + self.approve_file_change_request(request_id, params)?; + } + other => { + bail!("received unsupported server request: {other:?}"); + } + } + + Ok(()) + } + + fn handle_command_execution_request_approval( + &mut self, + request_id: RequestId, + params: CommandExecutionRequestApprovalParams, + ) -> Result<()> { + let CommandExecutionRequestApprovalParams { + thread_id, + turn_id, + item_id, + reason, + command, + cwd, + command_actions, + proposed_execpolicy_amendment, + } = params; + + println!( + "\n< commandExecution approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" + ); + if let Some(reason) = reason.as_deref() { + println!("< reason: {reason}"); + } + if let Some(command) = command.as_deref() { + println!("< command: {command}"); + } + if let Some(cwd) = cwd.as_ref() { + println!("< cwd: {}", cwd.display()); + } + if let Some(command_actions) = command_actions.as_ref() + && !command_actions.is_empty() + { + println!("< command actions: {command_actions:?}"); + } + if let Some(execpolicy_amendment) = proposed_execpolicy_amendment.as_ref() { + println!("< proposed execpolicy amendment: {execpolicy_amendment:?}"); + } + + let response = CommandExecutionRequestApprovalResponse { + decision: CommandExecutionApprovalDecision::Accept, + }; + self.send_server_request_response(request_id, &response)?; + println!("< approved commandExecution request for item {item_id}"); + Ok(()) + } + + fn approve_file_change_request( + &mut self, + request_id: RequestId, + params: FileChangeRequestApprovalParams, + ) -> Result<()> { + let FileChangeRequestApprovalParams { + thread_id, + turn_id, + item_id, + reason, + grant_root, + } = params; + + println!( + "\n< fileChange approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" + ); + if let Some(reason) = reason.as_deref() { + println!("< reason: {reason}"); + } + if let Some(grant_root) = grant_root.as_deref() { + println!("< grant root: {}", grant_root.display()); + } + + let response = FileChangeRequestApprovalResponse { + decision: FileChangeApprovalDecision::Accept, + }; + self.send_server_request_response(request_id, &response)?; + println!("< approved fileChange request for item {item_id}"); + Ok(()) + } + + fn send_server_request_response(&mut self, request_id: RequestId, response: &T) -> Result<()> + where + T: Serialize, + { + let message = JSONRPCMessage::Response(JSONRPCResponse { + id: request_id, + result: serde_json::to_value(response)?, + }); + self.write_jsonrpc_message(message) + } + + fn write_jsonrpc_message(&mut self, message: JSONRPCMessage) -> Result<()> { + let payload = serde_json::to_string(&message)?; + let pretty = serde_json::to_string_pretty(&message)?; + print_multiline_with_prefix("> ", &pretty); + + if let Some(stdin) = self.stdin.as_mut() { + writeln!(stdin, "{payload}")?; + stdin + .flush() + .context("failed to flush response to codex app-server")?; + return Ok(()); + } + + bail!("codex app-server stdin closed") + } +} + +fn print_multiline_with_prefix(prefix: &str, payload: &str) { + for line in payload.lines() { + println!("{prefix}{line}"); + } +} + +impl Drop for CodexClient { + fn drop(&mut self) { + let _ = self.stdin.take(); + + if let Ok(Some(status)) = self.child.try_wait() { + println!("[codex app-server exited: {status}]"); + return; + } + + thread::sleep(Duration::from_millis(100)); + + if let Ok(Some(status)) = self.child.try_wait() { + println!("[codex app-server exited: {status}]"); + return; + } + + let _ = self.child.kill(); + let _ = self.child.wait(); + } +} diff --git a/codex-rs/app-server-test-client/src/main.rs b/codex-rs/app-server-test-client/src/main.rs index 6d3fc06d81..a4da2e4026 100644 --- a/codex-rs/app-server-test-client/src/main.rs +++ b/codex-rs/app-server-test-client/src/main.rs @@ -1,1061 +1,5 @@ -use std::collections::VecDeque; -use std::fs; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Write; -use std::path::Path; -use std::process::Child; -use std::process::ChildStdin; -use std::process::ChildStdout; -use std::process::Command; -use std::process::Stdio; -use std::thread; -use std::time::Duration; - -use anyhow::Context; use anyhow::Result; -use anyhow::bail; -use clap::ArgAction; -use clap::Parser; -use clap::Subcommand; -use codex_app_server_protocol::AddConversationListenerParams; -use codex_app_server_protocol::AddConversationSubscriptionResponse; -use codex_app_server_protocol::AskForApproval; -use codex_app_server_protocol::ClientInfo; -use codex_app_server_protocol::ClientRequest; -use codex_app_server_protocol::CommandExecutionApprovalDecision; -use codex_app_server_protocol::CommandExecutionRequestApprovalParams; -use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; -use codex_app_server_protocol::DynamicToolSpec; -use codex_app_server_protocol::FileChangeApprovalDecision; -use codex_app_server_protocol::FileChangeRequestApprovalParams; -use codex_app_server_protocol::FileChangeRequestApprovalResponse; -use codex_app_server_protocol::GetAccountRateLimitsResponse; -use codex_app_server_protocol::InitializeCapabilities; -use codex_app_server_protocol::InitializeParams; -use codex_app_server_protocol::InitializeResponse; -use codex_app_server_protocol::InputItem; -use codex_app_server_protocol::JSONRPCMessage; -use codex_app_server_protocol::JSONRPCNotification; -use codex_app_server_protocol::JSONRPCRequest; -use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::LoginChatGptCompleteNotification; -use codex_app_server_protocol::LoginChatGptResponse; -use codex_app_server_protocol::ModelListParams; -use codex_app_server_protocol::ModelListResponse; -use codex_app_server_protocol::NewConversationParams; -use codex_app_server_protocol::NewConversationResponse; -use codex_app_server_protocol::RequestId; -use codex_app_server_protocol::SandboxPolicy; -use codex_app_server_protocol::SendUserMessageParams; -use codex_app_server_protocol::SendUserMessageResponse; -use codex_app_server_protocol::ServerNotification; -use codex_app_server_protocol::ServerRequest; -use codex_app_server_protocol::ThreadStartParams; -use codex_app_server_protocol::ThreadStartResponse; -use codex_app_server_protocol::TurnStartParams; -use codex_app_server_protocol::TurnStartResponse; -use codex_app_server_protocol::TurnStatus; -use codex_app_server_protocol::UserInput as V2UserInput; -use codex_protocol::ThreadId; -use codex_protocol::protocol::Event; -use codex_protocol::protocol::EventMsg; -use serde::Serialize; -use serde::de::DeserializeOwned; -use serde_json::Value; -use uuid::Uuid; - -/// Minimal launcher that initializes the Codex app-server and logs the handshake. -#[derive(Parser)] -#[command(author = "Codex", version, about = "Bootstrap Codex app-server", long_about = None)] -struct Cli { - /// Path to the `codex` CLI binary. - #[arg(long, env = "CODEX_BIN", default_value = "codex")] - codex_bin: String, - - /// Forwarded to the `codex` CLI as `--config key=value`. Repeatable. - /// - /// Example: - /// `--config 'model_providers.mock.base_url="http://localhost:4010/v2"'` - #[arg( - short = 'c', - long = "config", - value_name = "key=value", - action = ArgAction::Append, - global = true - )] - config_overrides: Vec, - - /// JSON array of dynamic tool specs or a single tool object. - /// Prefix a filename with '@' to read from a file. - /// - /// Example: - /// --dynamic-tools '[{"name":"demo","description":"Demo","inputSchema":{"type":"object"}}]' - /// --dynamic-tools @/path/to/tools.json - #[arg(long, value_name = "json-or-@file", global = true)] - dynamic_tools: Option, - - #[command(subcommand)] - command: CliCommand, -} - -#[derive(Subcommand)] -enum CliCommand { - /// Send a user message through the Codex app-server. - SendMessage { - /// User message to send to Codex. - #[arg()] - user_message: String, - }, - /// Send a user message through the app-server V2 thread/turn APIs. - SendMessageV2 { - /// User message to send to Codex. - #[arg()] - user_message: String, - }, - /// Start a V2 turn that elicits an ExecCommand approval. - #[command(name = "trigger-cmd-approval")] - TriggerCmdApproval { - /// Optional prompt; defaults to a simple python command. - #[arg()] - user_message: Option, - }, - /// Start a V2 turn that elicits an ApplyPatch approval. - #[command(name = "trigger-patch-approval")] - TriggerPatchApproval { - /// Optional prompt; defaults to creating a file via apply_patch. - #[arg()] - user_message: Option, - }, - /// Start a V2 turn that should not elicit an ExecCommand approval. - #[command(name = "no-trigger-cmd-approval")] - NoTriggerCmdApproval, - /// Send two sequential V2 turns in the same thread to test follow-up behavior. - SendFollowUpV2 { - /// Initial user message for the first turn. - #[arg()] - first_message: String, - /// Follow-up user message for the second turn. - #[arg()] - follow_up_message: String, - }, - /// Trigger the ChatGPT login flow and wait for completion. - TestLogin, - /// Fetch the current account rate limits from the Codex app-server. - GetAccountRateLimits, - /// List the available models from the Codex app-server. - #[command(name = "model-list")] - ModelList, -} fn main() -> Result<()> { - let Cli { - codex_bin, - config_overrides, - dynamic_tools, - command, - } = Cli::parse(); - - let dynamic_tools = parse_dynamic_tools_arg(&dynamic_tools)?; - - match command { - CliCommand::SendMessage { user_message } => { - ensure_dynamic_tools_unused(&dynamic_tools, "send-message")?; - send_message(&codex_bin, &config_overrides, user_message) - } - CliCommand::SendMessageV2 { user_message } => { - send_message_v2(&codex_bin, &config_overrides, user_message, &dynamic_tools) - } - CliCommand::TriggerCmdApproval { user_message } => { - trigger_cmd_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) - } - CliCommand::TriggerPatchApproval { user_message } => { - trigger_patch_approval(&codex_bin, &config_overrides, user_message, &dynamic_tools) - } - CliCommand::NoTriggerCmdApproval => { - no_trigger_cmd_approval(&codex_bin, &config_overrides, &dynamic_tools) - } - CliCommand::SendFollowUpV2 { - first_message, - follow_up_message, - } => send_follow_up_v2( - &codex_bin, - &config_overrides, - first_message, - follow_up_message, - &dynamic_tools, - ), - CliCommand::TestLogin => { - ensure_dynamic_tools_unused(&dynamic_tools, "test-login")?; - test_login(&codex_bin, &config_overrides) - } - CliCommand::GetAccountRateLimits => { - ensure_dynamic_tools_unused(&dynamic_tools, "get-account-rate-limits")?; - get_account_rate_limits(&codex_bin, &config_overrides) - } - CliCommand::ModelList => { - ensure_dynamic_tools_unused(&dynamic_tools, "model-list")?; - model_list(&codex_bin, &config_overrides) - } - } -} - -fn send_message(codex_bin: &str, config_overrides: &[String], user_message: String) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let conversation = client.start_thread()?; - println!("< newConversation response: {conversation:?}"); - - let subscription = client.add_conversation_listener(&conversation.conversation_id)?; - println!("< addConversationListener response: {subscription:?}"); - - let send_response = client.send_user_message(&conversation.conversation_id, &user_message)?; - println!("< sendUserMessage response: {send_response:?}"); - - client.stream_conversation(&conversation.conversation_id)?; - - client.remove_thread_listener(subscription.subscription_id)?; - - Ok(()) -} - -fn send_message_v2( - codex_bin: &str, - config_overrides: &[String], - user_message: String, - dynamic_tools: &Option>, -) -> Result<()> { - send_message_v2_with_policies( - codex_bin, - config_overrides, - user_message, - None, - None, - dynamic_tools, - ) -} - -fn trigger_cmd_approval( - codex_bin: &str, - config_overrides: &[String], - user_message: Option, - dynamic_tools: &Option>, -) -> Result<()> { - let default_prompt = - "Run `touch /tmp/should-trigger-approval` so I can confirm the file exists."; - let message = user_message.unwrap_or_else(|| default_prompt.to_string()); - send_message_v2_with_policies( - codex_bin, - config_overrides, - message, - Some(AskForApproval::OnRequest), - Some(SandboxPolicy::ReadOnly), - dynamic_tools, - ) -} - -fn trigger_patch_approval( - codex_bin: &str, - config_overrides: &[String], - user_message: Option, - dynamic_tools: &Option>, -) -> Result<()> { - let default_prompt = - "Create a file named APPROVAL_DEMO.txt containing a short hello message using apply_patch."; - let message = user_message.unwrap_or_else(|| default_prompt.to_string()); - send_message_v2_with_policies( - codex_bin, - config_overrides, - message, - Some(AskForApproval::OnRequest), - Some(SandboxPolicy::ReadOnly), - dynamic_tools, - ) -} - -fn no_trigger_cmd_approval( - codex_bin: &str, - config_overrides: &[String], - dynamic_tools: &Option>, -) -> Result<()> { - let prompt = "Run `touch should_not_trigger_approval.txt`"; - send_message_v2_with_policies( - codex_bin, - config_overrides, - prompt.to_string(), - None, - None, - dynamic_tools, - ) -} - -fn send_message_v2_with_policies( - codex_bin: &str, - config_overrides: &[String], - user_message: String, - approval_policy: Option, - sandbox_policy: Option, - dynamic_tools: &Option>, -) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let thread_response = client.thread_start(ThreadStartParams { - dynamic_tools: dynamic_tools.clone(), - ..Default::default() - })?; - println!("< thread/start response: {thread_response:?}"); - let mut turn_params = TurnStartParams { - thread_id: thread_response.thread.id.clone(), - input: vec![V2UserInput::Text { - text: user_message, - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - ..Default::default() - }; - turn_params.approval_policy = approval_policy; - turn_params.sandbox_policy = sandbox_policy; - - let turn_response = client.turn_start(turn_params)?; - println!("< turn/start response: {turn_response:?}"); - - client.stream_turn(&thread_response.thread.id, &turn_response.turn.id)?; - - Ok(()) -} - -fn send_follow_up_v2( - codex_bin: &str, - config_overrides: &[String], - first_message: String, - follow_up_message: String, - dynamic_tools: &Option>, -) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let thread_response = client.thread_start(ThreadStartParams { - dynamic_tools: dynamic_tools.clone(), - ..Default::default() - })?; - println!("< thread/start response: {thread_response:?}"); - - let first_turn_params = TurnStartParams { - thread_id: thread_response.thread.id.clone(), - input: vec![V2UserInput::Text { - text: first_message, - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - ..Default::default() - }; - let first_turn_response = client.turn_start(first_turn_params)?; - println!("< turn/start response (initial): {first_turn_response:?}"); - client.stream_turn(&thread_response.thread.id, &first_turn_response.turn.id)?; - - let follow_up_params = TurnStartParams { - thread_id: thread_response.thread.id.clone(), - input: vec![V2UserInput::Text { - text: follow_up_message, - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - ..Default::default() - }; - let follow_up_response = client.turn_start(follow_up_params)?; - println!("< turn/start response (follow-up): {follow_up_response:?}"); - client.stream_turn(&thread_response.thread.id, &follow_up_response.turn.id)?; - - Ok(()) -} - -fn test_login(codex_bin: &str, config_overrides: &[String]) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let login_response = client.login_chat_gpt()?; - println!("< loginChatGpt response: {login_response:?}"); - println!( - "Open the following URL in your browser to continue:\n{}", - login_response.auth_url - ); - - let completion = client.wait_for_login_completion(&login_response.login_id)?; - println!("< loginChatGptComplete notification: {completion:?}"); - - if completion.success { - println!("Login succeeded."); - Ok(()) - } else { - bail!( - "login failed: {}", - completion - .error - .as_deref() - .unwrap_or("unknown error from loginChatGptComplete") - ); - } -} - -fn get_account_rate_limits(codex_bin: &str, config_overrides: &[String]) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let response = client.get_account_rate_limits()?; - println!("< account/rateLimits/read response: {response:?}"); - - Ok(()) -} - -fn model_list(codex_bin: &str, config_overrides: &[String]) -> Result<()> { - let mut client = CodexClient::spawn(codex_bin, config_overrides)?; - - let initialize = client.initialize()?; - println!("< initialize response: {initialize:?}"); - - let response = client.model_list(ModelListParams::default())?; - println!("< model/list response: {response:?}"); - - Ok(()) -} - -fn ensure_dynamic_tools_unused( - dynamic_tools: &Option>, - command: &str, -) -> Result<()> { - if dynamic_tools.is_some() { - bail!( - "dynamic tools are only supported for v2 thread/start; remove --dynamic-tools for {command} or use send-message-v2" - ); - } - Ok(()) -} - -fn parse_dynamic_tools_arg(dynamic_tools: &Option) -> Result>> { - let Some(raw_arg) = dynamic_tools.as_deref() else { - return Ok(None); - }; - - let raw_json = if let Some(path) = raw_arg.strip_prefix('@') { - fs::read_to_string(Path::new(path)) - .with_context(|| format!("read dynamic tools file {path}"))? - } else { - raw_arg.to_string() - }; - - let value: Value = serde_json::from_str(&raw_json).context("parse dynamic tools JSON")?; - let tools = match value { - Value::Array(_) => serde_json::from_value(value).context("decode dynamic tools array")?, - Value::Object(_) => vec![serde_json::from_value(value).context("decode dynamic tool")?], - _ => bail!("dynamic tools JSON must be an object or array"), - }; - - Ok(Some(tools)) -} - -struct CodexClient { - child: Child, - stdin: Option, - stdout: BufReader, - pending_notifications: VecDeque, -} - -impl CodexClient { - fn spawn(codex_bin: &str, config_overrides: &[String]) -> Result { - let mut cmd = Command::new(codex_bin); - for override_kv in config_overrides { - cmd.arg("--config").arg(override_kv); - } - let mut codex_app_server = cmd - .arg("app-server") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - .with_context(|| format!("failed to start `{codex_bin}` app-server"))?; - - let stdin = codex_app_server - .stdin - .take() - .context("codex app-server stdin unavailable")?; - let stdout = codex_app_server - .stdout - .take() - .context("codex app-server stdout unavailable")?; - - Ok(Self { - child: codex_app_server, - stdin: Some(stdin), - stdout: BufReader::new(stdout), - pending_notifications: VecDeque::new(), - }) - } - - fn initialize(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::Initialize { - request_id: request_id.clone(), - params: InitializeParams { - client_info: ClientInfo { - name: "codex-toy-app-server".to_string(), - title: Some("Codex Toy App Server".to_string()), - version: env!("CARGO_PKG_VERSION").to_string(), - }, - capabilities: Some(InitializeCapabilities { - experimental_api: true, - }), - }, - }; - - self.send_request(request, request_id, "initialize") - } - - fn start_thread(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::NewConversation { - request_id: request_id.clone(), - params: NewConversationParams::default(), - }; - - self.send_request(request, request_id, "newConversation") - } - - fn add_conversation_listener( - &mut self, - conversation_id: &ThreadId, - ) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::AddConversationListener { - request_id: request_id.clone(), - params: AddConversationListenerParams { - conversation_id: *conversation_id, - experimental_raw_events: false, - }, - }; - - self.send_request(request, request_id, "addConversationListener") - } - - fn remove_thread_listener(&mut self, subscription_id: Uuid) -> Result<()> { - let request_id = self.request_id(); - let request = ClientRequest::RemoveConversationListener { - request_id: request_id.clone(), - params: codex_app_server_protocol::RemoveConversationListenerParams { subscription_id }, - }; - - self.send_request::( - request, - request_id, - "removeConversationListener", - )?; - - Ok(()) - } - - fn send_user_message( - &mut self, - conversation_id: &ThreadId, - message: &str, - ) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::SendUserMessage { - request_id: request_id.clone(), - params: SendUserMessageParams { - conversation_id: *conversation_id, - items: vec![InputItem::Text { - text: message.to_string(), - // Test client sends plain text without UI element ranges. - text_elements: Vec::new(), - }], - }, - }; - - self.send_request(request, request_id, "sendUserMessage") - } - - fn thread_start(&mut self, params: ThreadStartParams) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::ThreadStart { - request_id: request_id.clone(), - params, - }; - - self.send_request(request, request_id, "thread/start") - } - - fn turn_start(&mut self, params: TurnStartParams) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::TurnStart { - request_id: request_id.clone(), - params, - }; - - self.send_request(request, request_id, "turn/start") - } - - fn login_chat_gpt(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::LoginChatGpt { - request_id: request_id.clone(), - params: None, - }; - - self.send_request(request, request_id, "loginChatGpt") - } - - fn get_account_rate_limits(&mut self) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::GetAccountRateLimits { - request_id: request_id.clone(), - params: None, - }; - - self.send_request(request, request_id, "account/rateLimits/read") - } - - fn model_list(&mut self, params: ModelListParams) -> Result { - let request_id = self.request_id(); - let request = ClientRequest::ModelList { - request_id: request_id.clone(), - params, - }; - - self.send_request(request, request_id, "model/list") - } - - fn stream_conversation(&mut self, conversation_id: &ThreadId) -> Result<()> { - loop { - let notification = self.next_notification()?; - - if !notification.method.starts_with("codex/event/") { - continue; - } - - if let Some(event) = self.extract_event(notification, conversation_id)? { - match &event.msg { - EventMsg::AgentMessage(event) => { - println!("{}", event.message); - } - EventMsg::AgentMessageDelta(event) => { - print!("{}", event.delta); - std::io::stdout().flush().ok(); - } - EventMsg::TurnComplete(event) => { - println!("\n[task complete: {event:?}]"); - break; - } - EventMsg::TurnAborted(event) => { - println!("\n[turn aborted: {:?}]", event.reason); - break; - } - EventMsg::Error(event) => { - println!("[error] {event:?}"); - } - _ => { - println!("[UNKNOWN EVENT] {:?}", event.msg); - } - } - } - } - - Ok(()) - } - - fn wait_for_login_completion( - &mut self, - expected_login_id: &Uuid, - ) -> Result { - loop { - let notification = self.next_notification()?; - - if let Ok(server_notification) = ServerNotification::try_from(notification) { - match server_notification { - ServerNotification::LoginChatGptComplete(completion) => { - if &completion.login_id == expected_login_id { - return Ok(completion); - } - - println!( - "[ignoring loginChatGptComplete for unexpected login_id: {}]", - completion.login_id - ); - } - ServerNotification::AuthStatusChange(status) => { - println!("< authStatusChange notification: {status:?}"); - } - ServerNotification::AccountRateLimitsUpdated(snapshot) => { - println!("< accountRateLimitsUpdated notification: {snapshot:?}"); - } - ServerNotification::SessionConfigured(_) => { - // SessionConfigured notifications are unrelated to login; skip. - } - _ => {} - } - } - - // Not a server notification (likely a conversation event); keep waiting. - } - } - - fn stream_turn(&mut self, thread_id: &str, turn_id: &str) -> Result<()> { - loop { - let notification = self.next_notification()?; - - let Ok(server_notification) = ServerNotification::try_from(notification) else { - continue; - }; - - match server_notification { - ServerNotification::ThreadStarted(payload) => { - if payload.thread.id == thread_id { - println!("< thread/started notification: {:?}", payload.thread); - } - } - ServerNotification::TurnStarted(payload) => { - if payload.turn.id == turn_id { - println!("< turn/started notification: {:?}", payload.turn.status); - } - } - ServerNotification::AgentMessageDelta(delta) => { - print!("{}", delta.delta); - std::io::stdout().flush().ok(); - } - ServerNotification::CommandExecutionOutputDelta(delta) => { - print!("{}", delta.delta); - std::io::stdout().flush().ok(); - } - ServerNotification::TerminalInteraction(delta) => { - println!("[stdin sent: {}]", delta.stdin); - std::io::stdout().flush().ok(); - } - ServerNotification::ItemStarted(payload) => { - println!("\n< item started: {:?}", payload.item); - } - ServerNotification::ItemCompleted(payload) => { - println!("< item completed: {:?}", payload.item); - } - ServerNotification::TurnCompleted(payload) => { - if payload.turn.id == turn_id { - println!("\n< turn/completed notification: {:?}", payload.turn.status); - if payload.turn.status == TurnStatus::Failed - && let Some(error) = payload.turn.error - { - println!("[turn error] {}", error.message); - } - break; - } - } - ServerNotification::McpToolCallProgress(payload) => { - println!("< MCP tool progress: {}", payload.message); - } - _ => { - println!("[UNKNOWN SERVER NOTIFICATION] {server_notification:?}"); - } - } - } - - Ok(()) - } - - fn extract_event( - &self, - notification: JSONRPCNotification, - conversation_id: &ThreadId, - ) -> Result> { - let params = notification - .params - .context("event notification missing params")?; - - let mut map = match params { - Value::Object(map) => map, - other => bail!("unexpected params shape: {other:?}"), - }; - - let conversation_value = map - .remove("conversationId") - .context("event missing conversationId")?; - let notification_conversation: ThreadId = serde_json::from_value(conversation_value) - .context("conversationId was not a valid UUID")?; - - if ¬ification_conversation != conversation_id { - return Ok(None); - } - - let event_value = Value::Object(map); - let event: Event = - serde_json::from_value(event_value).context("failed to decode event payload")?; - Ok(Some(event)) - } - - fn send_request( - &mut self, - request: ClientRequest, - request_id: RequestId, - method: &str, - ) -> Result - where - T: DeserializeOwned, - { - self.write_request(&request)?; - self.wait_for_response(request_id, method) - } - - fn write_request(&mut self, request: &ClientRequest) -> Result<()> { - let request_json = serde_json::to_string(request)?; - let request_pretty = serde_json::to_string_pretty(request)?; - print_multiline_with_prefix("> ", &request_pretty); - - if let Some(stdin) = self.stdin.as_mut() { - writeln!(stdin, "{request_json}")?; - stdin - .flush() - .context("failed to flush request to codex app-server")?; - } else { - bail!("codex app-server stdin closed"); - } - - Ok(()) - } - - fn wait_for_response(&mut self, request_id: RequestId, method: &str) -> Result - where - T: DeserializeOwned, - { - loop { - let message = self.read_jsonrpc_message()?; - - match message { - JSONRPCMessage::Response(JSONRPCResponse { id, result }) => { - if id == request_id { - return serde_json::from_value(result) - .with_context(|| format!("{method} response missing payload")); - } - } - JSONRPCMessage::Error(err) => { - if err.id == request_id { - bail!("{method} failed: {err:?}"); - } - } - JSONRPCMessage::Notification(notification) => { - self.pending_notifications.push_back(notification); - } - JSONRPCMessage::Request(request) => { - self.handle_server_request(request)?; - } - } - } - } - - fn next_notification(&mut self) -> Result { - if let Some(notification) = self.pending_notifications.pop_front() { - return Ok(notification); - } - - loop { - let message = self.read_jsonrpc_message()?; - - match message { - JSONRPCMessage::Notification(notification) => return Ok(notification), - JSONRPCMessage::Response(_) | JSONRPCMessage::Error(_) => { - // No outstanding requests, so ignore stray responses/errors for now. - continue; - } - JSONRPCMessage::Request(request) => { - self.handle_server_request(request)?; - } - } - } - } - - fn read_jsonrpc_message(&mut self) -> Result { - loop { - let mut response_line = String::new(); - let bytes = self - .stdout - .read_line(&mut response_line) - .context("failed to read from codex app-server")?; - - if bytes == 0 { - bail!("codex app-server closed stdout"); - } - - let trimmed = response_line.trim(); - if trimmed.is_empty() { - continue; - } - - let parsed: Value = - serde_json::from_str(trimmed).context("response was not valid JSON-RPC")?; - let pretty = serde_json::to_string_pretty(&parsed)?; - print_multiline_with_prefix("< ", &pretty); - let message: JSONRPCMessage = serde_json::from_value(parsed) - .context("response was not a valid JSON-RPC message")?; - return Ok(message); - } - } - - fn request_id(&self) -> RequestId { - RequestId::String(Uuid::new_v4().to_string()) - } - - fn handle_server_request(&mut self, request: JSONRPCRequest) -> Result<()> { - let server_request = ServerRequest::try_from(request) - .context("failed to deserialize ServerRequest from JSONRPCRequest")?; - - match server_request { - ServerRequest::CommandExecutionRequestApproval { request_id, params } => { - self.handle_command_execution_request_approval(request_id, params)?; - } - ServerRequest::FileChangeRequestApproval { request_id, params } => { - self.approve_file_change_request(request_id, params)?; - } - other => { - bail!("received unsupported server request: {other:?}"); - } - } - - Ok(()) - } - - fn handle_command_execution_request_approval( - &mut self, - request_id: RequestId, - params: CommandExecutionRequestApprovalParams, - ) -> Result<()> { - let CommandExecutionRequestApprovalParams { - thread_id, - turn_id, - item_id, - reason, - command, - cwd, - command_actions, - proposed_execpolicy_amendment, - } = params; - - println!( - "\n< commandExecution approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" - ); - if let Some(reason) = reason.as_deref() { - println!("< reason: {reason}"); - } - if let Some(command) = command.as_deref() { - println!("< command: {command}"); - } - if let Some(cwd) = cwd.as_ref() { - println!("< cwd: {}", cwd.display()); - } - if let Some(command_actions) = command_actions.as_ref() - && !command_actions.is_empty() - { - println!("< command actions: {command_actions:?}"); - } - if let Some(execpolicy_amendment) = proposed_execpolicy_amendment.as_ref() { - println!("< proposed execpolicy amendment: {execpolicy_amendment:?}"); - } - - let response = CommandExecutionRequestApprovalResponse { - decision: CommandExecutionApprovalDecision::Accept, - }; - self.send_server_request_response(request_id, &response)?; - println!("< approved commandExecution request for item {item_id}"); - Ok(()) - } - - fn approve_file_change_request( - &mut self, - request_id: RequestId, - params: FileChangeRequestApprovalParams, - ) -> Result<()> { - let FileChangeRequestApprovalParams { - thread_id, - turn_id, - item_id, - reason, - grant_root, - } = params; - - println!( - "\n< fileChange approval requested for thread {thread_id}, turn {turn_id}, item {item_id}" - ); - if let Some(reason) = reason.as_deref() { - println!("< reason: {reason}"); - } - if let Some(grant_root) = grant_root.as_deref() { - println!("< grant root: {}", grant_root.display()); - } - - let response = FileChangeRequestApprovalResponse { - decision: FileChangeApprovalDecision::Accept, - }; - self.send_server_request_response(request_id, &response)?; - println!("< approved fileChange request for item {item_id}"); - Ok(()) - } - - fn send_server_request_response(&mut self, request_id: RequestId, response: &T) -> Result<()> - where - T: Serialize, - { - let message = JSONRPCMessage::Response(JSONRPCResponse { - id: request_id, - result: serde_json::to_value(response)?, - }); - self.write_jsonrpc_message(message) - } - - fn write_jsonrpc_message(&mut self, message: JSONRPCMessage) -> Result<()> { - let payload = serde_json::to_string(&message)?; - let pretty = serde_json::to_string_pretty(&message)?; - print_multiline_with_prefix("> ", &pretty); - - if let Some(stdin) = self.stdin.as_mut() { - writeln!(stdin, "{payload}")?; - stdin - .flush() - .context("failed to flush response to codex app-server")?; - return Ok(()); - } - - bail!("codex app-server stdin closed") - } -} - -fn print_multiline_with_prefix(prefix: &str, payload: &str) { - for line in payload.lines() { - println!("{prefix}{line}"); - } -} - -impl Drop for CodexClient { - fn drop(&mut self) { - let _ = self.stdin.take(); - - if let Ok(Some(status)) = self.child.try_wait() { - println!("[codex app-server exited: {status}]"); - return; - } - - thread::sleep(Duration::from_millis(100)); - - if let Ok(Some(status)) = self.child.try_wait() { - println!("[codex app-server exited: {status}]"); - return; - } - - let _ = self.child.kill(); - let _ = self.child.wait(); - } + codex_app_server_test_client::run() } diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index cfcfce88f9..77344df477 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -21,6 +21,7 @@ clap = { workspace = true, features = ["derive"] } clap_complete = { workspace = true } codex-app-server = { workspace = true } codex-app-server-protocol = { workspace = true } +codex-app-server-test-client = { workspace = true } codex-arg0 = { workspace = true } codex-chatgpt = { workspace = true } codex-cloud-tasks = { path = "../cloud-tasks" } diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 022d6bbdf5..defc063eb6 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -110,9 +110,11 @@ enum Subcommand { Completion(CompletionCommand), /// Run commands within a Codex-provided sandbox. - #[clap(visible_alias = "debug")] Sandbox(SandboxArgs), + /// Debugging tools. + Debug(DebugCommand), + /// Execpolicy tooling. #[clap(hide = true)] Execpolicy(ExecpolicyCommand), @@ -150,6 +152,36 @@ struct CompletionCommand { shell: Shell, } +#[derive(Debug, Parser)] +struct DebugCommand { + #[command(subcommand)] + subcommand: DebugSubcommand, +} + +#[derive(Debug, clap::Subcommand)] +enum DebugSubcommand { + /// Tooling: helps debug the app server. + AppServer(DebugAppServerCommand), +} + +#[derive(Debug, Parser)] +struct DebugAppServerCommand { + #[command(subcommand)] + subcommand: DebugAppServerSubcommand, +} + +#[derive(Debug, clap::Subcommand)] +enum DebugAppServerSubcommand { + // Send message to app server V2. + SendMessageV2(DebugAppServerSendMessageV2Command), +} + +#[derive(Debug, Parser)] +struct DebugAppServerSendMessageV2Command { + #[arg(value_name = "USER_MESSAGE", required = true)] + user_message: String, +} + #[derive(Debug, Parser)] struct ResumeCommand { /// Conversation/session id (UUID) or thread name. UUIDs take precedence if it parses. @@ -425,6 +457,15 @@ fn run_execpolicycheck(cmd: ExecPolicyCheckCommand) -> anyhow::Result<()> { cmd.run() } +fn run_debug_app_server_command(cmd: DebugAppServerCommand) -> anyhow::Result<()> { + match cmd.subcommand { + DebugAppServerSubcommand::SendMessageV2(cmd) => { + let codex_bin = std::env::current_exe()?; + codex_app_server_test_client::send_message_v2(&codex_bin, &[], cmd.user_message, &None) + } + } +} + #[derive(Debug, Default, Parser, Clone)] struct FeatureToggles { /// Enable a feature (repeatable). Equivalent to `-c features.=true`. @@ -693,6 +734,11 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() .await?; } }, + Some(Subcommand::Debug(DebugCommand { subcommand })) => match subcommand { + DebugSubcommand::AppServer(cmd) => { + run_debug_app_server_command(cmd)?; + } + }, Some(Subcommand::Execpolicy(ExecpolicyCommand { sub })) => match sub { ExecpolicySubcommand::Check(cmd) => run_execpolicycheck(cmd)?, }, From a9eb766f33953d651a0f01670d893a4cf5da3763 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Tue, 3 Feb 2026 16:17:06 -0800 Subject: [PATCH 05/90] tui: make Esc clear request_user_input notes while notes are shown (#10569) ## Summary This PR updates the `request_user_input` TUI overlay so `Esc` is context-aware: - When notes are visible for an option question, `Esc` now clears notes and exits notes mode. - When notes are not visible (normal option selection UI), `Esc` still interrupts as before. It also updates footer guidance text to match behavior. ## Changes - Added a shared notes-clear path for option questions: - `Tab` and `Esc` now both clear notes and return focus to options when notes are visible. - Updated footer hint text in notes-visible state: - from: `tab to clear notes | ... | esc to interrupt` - to: `tab or esc to clear notes | ...` - Hid `esc to interrupt` hint while notes are visible for option questions. - Kept `esc to interrupt` visible and functional in normal option-selection mode. - Updated tests to assert the new `Esc` behavior in notes mode. - Updated snapshot output for the notes-visible footer row. - Updated docs in `docs/tui-request-user-input.md` to reflect mode-specific `Esc` behavior. --- .../src/bottom_pane/request_user_input/mod.rs | 66 +++++++++++++------ ...uest_user_input_options_notes_visible.snap | 3 +- docs/tui-request-user-input.md | 4 +- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs index 59807a8a4c..9542772446 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs +++ b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs @@ -427,8 +427,8 @@ impl RequestUserInputOverlay { if self.selected_option_index().is_some() && !notes_visible { tips.push(FooterTip::highlighted("tab to add notes")); } - if self.selected_option_index().is_some() && notes_visible && self.focus_is_notes() { - tips.push(FooterTip::new("tab to clear notes")); + if self.selected_option_index().is_some() && notes_visible { + tips.push(FooterTip::new("tab or esc to clear notes")); } } @@ -449,7 +449,9 @@ impl RequestUserInputOverlay { tips.push(FooterTip::new("ctrl + n next question")); } } - tips.push(FooterTip::new("esc to interrupt")); + if !(self.has_options() && notes_visible) { + tips.push(FooterTip::new("esc to interrupt")); + } tips } @@ -663,6 +665,23 @@ impl RequestUserInputOverlay { self.sync_composer_placeholder(); } + fn clear_notes_and_focus_options(&mut self) { + if !self.has_options() { + return; + } + if let Some(answer) = self.current_answer_mut() { + answer.draft = ComposerDraft::default(); + answer.answer_committed = false; + answer.notes_visible = false; + } + self.pending_submission_draft = None; + self.composer + .set_text_content(String::new(), Vec::new(), Vec::new()); + self.composer.move_cursor_to_end(); + self.focus = Focus::Options; + self.sync_composer_placeholder(); + } + /// Ensure there is a selection before allowing notes entry. fn ensure_selected_for_notes(&mut self) { if let Some(answer) = self.current_answer_mut() { @@ -976,6 +995,10 @@ impl BottomPaneView for RequestUserInputOverlay { } if matches!(key_event.code, KeyCode::Esc) { + if self.has_options() && self.notes_ui_visible() { + self.clear_notes_and_focus_options(); + return; + } // TODO: Emit interrupted request_user_input results (including committed answers) // once core supports persisting them reliably without follow-up turn issues. self.app_event_tx.send(AppEvent::CodexOp(Op::Interrupt)); @@ -1093,16 +1116,7 @@ impl BottomPaneView for RequestUserInputOverlay { Focus::Notes => { let notes_empty = self.composer.current_text_with_pending().trim().is_empty(); if self.has_options() && matches!(key_event.code, KeyCode::Tab) { - if let Some(answer) = self.current_answer_mut() { - answer.draft = ComposerDraft::default(); - answer.answer_committed = false; - answer.notes_visible = false; - } - self.composer - .set_text_content(String::new(), Vec::new(), Vec::new()); - self.composer.move_cursor_to_end(); - self.focus = Focus::Options; - self.sync_composer_placeholder(); + self.clear_notes_and_focus_options(); return; } if self.has_options() && matches!(key_event.code, KeyCode::Backspace) && notes_empty @@ -1753,7 +1767,7 @@ mod tests { } #[test] - fn esc_in_notes_mode_interrupts() { + fn esc_in_notes_mode_clears_notes_and_hides_ui() { let (tx, mut rx) = test_sender(); let mut overlay = RequestUserInputOverlay::new( request_event("turn-1", vec![question_with_options("q1", "Pick one")]), @@ -1769,12 +1783,19 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Tab)); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); - assert_eq!(overlay.done, true); - expect_interrupt_only(&mut rx); + let answer = overlay.current_answer().expect("answer missing"); + assert_eq!(overlay.done, false); + assert!(matches!(overlay.focus, Focus::Options)); + assert_eq!(overlay.notes_ui_visible(), false); + assert_eq!(overlay.composer.current_text_with_pending(), ""); + assert_eq!(answer.draft.text, ""); + assert_eq!(answer.options_state.selected_idx, Some(0)); + assert_eq!(answer.answer_committed, false); + assert!(rx.try_recv().is_err()); } #[test] - fn esc_in_notes_mode_interrupts_with_notes_visible() { + fn esc_in_notes_mode_with_text_clears_notes_and_hides_ui() { let (tx, mut rx) = test_sender(); let mut overlay = RequestUserInputOverlay::new( request_event("turn-1", vec![question_with_options("q1", "Pick one")]), @@ -1791,8 +1812,15 @@ mod tests { overlay.handle_key_event(KeyEvent::from(KeyCode::Char('a'))); overlay.handle_key_event(KeyEvent::from(KeyCode::Esc)); - assert_eq!(overlay.done, true); - expect_interrupt_only(&mut rx); + let answer = overlay.current_answer().expect("answer missing"); + assert_eq!(overlay.done, false); + assert!(matches!(overlay.focus, Focus::Options)); + assert_eq!(overlay.notes_ui_visible(), false); + assert_eq!(overlay.composer.current_text_with_pending(), ""); + assert_eq!(answer.draft.text, ""); + assert_eq!(answer.options_state.selected_idx, Some(0)); + assert_eq!(answer.answer_committed, false); + assert!(rx.try_recv().is_err()); } #[test] diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap index d9d219b627..a4540a2b26 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_options_notes_visible.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2321 expression: "render_snapshot(&overlay, area)" --- @@ -16,4 +17,4 @@ expression: "render_snapshot(&overlay, area)" - tab to clear notes | enter to submit answer | esc to interrupt + tab or esc to clear notes | enter to submit answer diff --git a/docs/tui-request-user-input.md b/docs/tui-request-user-input.md index f8eb7aeb73..8ca6f5369b 100644 --- a/docs/tui-request-user-input.md +++ b/docs/tui-request-user-input.md @@ -30,7 +30,9 @@ friction for freeform input. - Enter advances to the next question. - Enter on the last question submits all answers. - PageUp/PageDown navigate across questions (when multiple are present). -- Esc interrupts the run. +- Esc interrupts the run in option selection mode. +- When notes are open for an option question, Tab or Esc clears notes and returns + to option selection. ## Layout priorities From fcaed4cb88f068ffaa3cb9cfc69e7cf6605de0ce Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Tue, 3 Feb 2026 18:04:07 -0800 Subject: [PATCH 06/90] feat: log webscocket timing into runtime metrics (#10577) --- codex-rs/core/src/client.rs | 8 ++ codex-rs/core/src/lib.rs | 1 + .../core/tests/suite/client_websockets.rs | 77 +++++++++++++++++++ codex-rs/otel/src/metrics/names.rs | 4 + codex-rs/otel/src/metrics/runtime_metrics.rs | 12 +++ codex-rs/otel/src/traces/otel_manager.rs | 38 +++++++++ codex-rs/otel/tests/suite/runtime_summary.rs | 14 +++- codex-rs/tui/src/history_cell.rs | 14 +++- 8 files changed, 165 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index fcc308f380..314bfe7b40 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -72,6 +72,8 @@ use crate::transport_manager::TransportManager; pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; +pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = + "x-responsesapi-include-timing-metrics"; #[derive(Debug, Default)] struct TurnMetadataCache { @@ -489,6 +491,12 @@ impl ModelClientSession { if needs_new { let mut headers = options.extra_headers.clone(); headers.extend(build_conversation_headers(options.conversation_id.clone())); + if self.state.config.features.enabled(Feature::RuntimeMetrics) { + headers.insert( + X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER, + HeaderValue::from_static("true"), + ); + } let websocket_telemetry = self.build_websocket_telemetry(); let new_conn: ApiWebSocketConnection = ApiWebSocketResponsesClient::new(api_provider, api_auth) diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index c9d67e19f1..c5ac0d8f85 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -61,6 +61,7 @@ pub mod token_data; mod truncate; mod unified_exec; pub mod windows_sandbox; +pub use client::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER; pub use model_provider_info::DEFAULT_LMSTUDIO_PORT; pub use model_provider_info::DEFAULT_OLLAMA_PORT; pub use model_provider_info::LMSTUDIO_OSS_PROVIDER_ID; diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 51c94edd7b..9f66283442 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -10,6 +10,7 @@ use codex_core::ResponseEvent; use codex_core::ResponseItem; use codex_core::TransportManager; use codex_core::WireApi; +use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER; use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_core::protocol::SessionSource; @@ -102,6 +103,72 @@ async fn responses_websocket_emits_websocket_telemetry_events() { server.shutdown().await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics_enabled() { + skip_if_no_network!(); + + let server = start_websocket_server(vec![vec![vec![ + ev_response_created("resp-1"), + serde_json::json!({ + "type": "responsesapi.websocket_timing", + "timing_metrics": { + "responses_duration_excl_engine_and_client_tool_time_ms": 120, + "engine_service_total_ms": 450 + } + }), + ev_completed("resp-1"), + ]]]) + .await; + + let harness = websocket_harness_with_runtime_metrics(&server, true).await; + harness.otel_manager.reset_runtime_metrics(); + let mut session = harness.client.new_session(None); + let prompt = prompt_with_input(vec![message_item("hello")]); + + stream_until_complete(&mut session, &prompt).await; + tokio::time::sleep(Duration::from_millis(10)).await; + + let handshake = server.single_handshake(); + assert_eq!( + handshake.header(X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER), + Some("true".to_string()) + ); + + let summary = harness + .otel_manager + .runtime_metrics_summary() + .expect("runtime metrics summary"); + assert_eq!(summary.responses_api_overhead_ms, 120); + assert_eq!(summary.responses_api_inference_time_ms, 450); + + server.shutdown().await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_websocket_omits_timing_metrics_header_when_runtime_metrics_disabled() { + skip_if_no_network!(); + + let server = start_websocket_server(vec![vec![vec![ + ev_response_created("resp-1"), + ev_completed("resp-1"), + ]]]) + .await; + + let harness = websocket_harness_with_runtime_metrics(&server, false).await; + let mut session = harness.client.new_session(None); + let prompt = prompt_with_input(vec![message_item("hello")]); + + stream_until_complete(&mut session, &prompt).await; + + let handshake = server.single_handshake(); + assert_eq!( + handshake.header(X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER), + None + ); + + server.shutdown().await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn responses_websocket_emits_reasoning_included_event() { skip_if_no_network!(); @@ -241,11 +308,21 @@ fn websocket_provider(server: &WebSocketTestServer) -> ModelProviderInfo { } async fn websocket_harness(server: &WebSocketTestServer) -> WebsocketTestHarness { + websocket_harness_with_runtime_metrics(server, false).await +} + +async fn websocket_harness_with_runtime_metrics( + server: &WebSocketTestServer, + runtime_metrics_enabled: bool, +) -> WebsocketTestHarness { let provider = websocket_provider(server); let codex_home = TempDir::new().unwrap(); let mut config = load_default_config_for_test(&codex_home).await; config.model = Some(MODEL.to_string()); config.features.enable(Feature::ResponsesWebsockets); + if runtime_metrics_enabled { + config.features.enable(Feature::RuntimeMetrics); + } let config = Arc::new(config); let model_info = ModelsManager::construct_model_info_offline(MODEL, &config); let conversation_id = ThreadId::new(); diff --git a/codex-rs/otel/src/metrics/names.rs b/codex-rs/otel/src/metrics/names.rs index b8ff9364ad..97c4f4e6ed 100644 --- a/codex-rs/otel/src/metrics/names.rs +++ b/codex-rs/otel/src/metrics/names.rs @@ -8,3 +8,7 @@ pub(crate) const WEBSOCKET_REQUEST_COUNT_METRIC: &str = "codex.websocket.request pub(crate) const WEBSOCKET_REQUEST_DURATION_METRIC: &str = "codex.websocket.request.duration_ms"; pub(crate) const WEBSOCKET_EVENT_COUNT_METRIC: &str = "codex.websocket.event"; pub(crate) const WEBSOCKET_EVENT_DURATION_METRIC: &str = "codex.websocket.event.duration_ms"; +pub(crate) const RESPONSES_API_OVERHEAD_DURATION_METRIC: &str = + "codex.responses_api_overhead.duration_ms"; +pub(crate) const RESPONSES_API_INFERENCE_TIME_DURATION_METRIC: &str = + "codex.responses_api_inference_time.duration_ms"; diff --git a/codex-rs/otel/src/metrics/runtime_metrics.rs b/codex-rs/otel/src/metrics/runtime_metrics.rs index dbd28010f6..d59ff4b1b7 100644 --- a/codex-rs/otel/src/metrics/runtime_metrics.rs +++ b/codex-rs/otel/src/metrics/runtime_metrics.rs @@ -1,5 +1,7 @@ use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_OVERHEAD_DURATION_METRIC; use crate::metrics::names::SSE_EVENT_COUNT_METRIC; use crate::metrics::names::SSE_EVENT_DURATION_METRIC; use crate::metrics::names::TOOL_CALL_COUNT_METRIC; @@ -32,6 +34,8 @@ pub struct RuntimeMetricsSummary { pub streaming_events: RuntimeMetricTotals, pub websocket_calls: RuntimeMetricTotals, pub websocket_events: RuntimeMetricTotals, + pub responses_api_overhead_ms: u64, + pub responses_api_inference_time_ms: u64, } impl RuntimeMetricsSummary { @@ -41,6 +45,8 @@ impl RuntimeMetricsSummary { && self.streaming_events.is_empty() && self.websocket_calls.is_empty() && self.websocket_events.is_empty() + && self.responses_api_overhead_ms == 0 + && self.responses_api_inference_time_ms == 0 } pub(crate) fn from_snapshot(snapshot: &ResourceMetrics) -> Self { @@ -64,12 +70,18 @@ impl RuntimeMetricsSummary { count: sum_counter(snapshot, WEBSOCKET_EVENT_COUNT_METRIC), duration_ms: sum_histogram_ms(snapshot, WEBSOCKET_EVENT_DURATION_METRIC), }; + let responses_api_overhead_ms = + sum_histogram_ms(snapshot, RESPONSES_API_OVERHEAD_DURATION_METRIC); + let responses_api_inference_time_ms = + sum_histogram_ms(snapshot, RESPONSES_API_INFERENCE_TIME_DURATION_METRIC); Self { tool_calls, api_calls, streaming_events, websocket_calls, websocket_events, + responses_api_overhead_ms, + responses_api_inference_time_ms, } } } diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index c585ec67d5..bde9515c20 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -1,5 +1,7 @@ use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_OVERHEAD_DURATION_METRIC; use crate::metrics::names::SSE_EVENT_COUNT_METRIC; use crate::metrics::names::SSE_EVENT_DURATION_METRIC; use crate::metrics::names::TOOL_CALL_COUNT_METRIC; @@ -42,6 +44,10 @@ pub use crate::ToolDecisionSource; const SSE_UNKNOWN_KIND: &str = "unknown"; const WEBSOCKET_UNKNOWN_KIND: &str = "unknown"; +const RESPONSES_WEBSOCKET_TIMING_KIND: &str = "responsesapi.websocket_timing"; +const RESPONSES_WEBSOCKET_TIMING_METRICS_FIELD: &str = "timing_metrics"; +const RESPONSES_API_OVERHEAD_FIELD: &str = "responses_duration_excl_engine_and_client_tool_time_ms"; +const RESPONSES_API_INFERENCE_FIELD: &str = "engine_service_total_ms"; impl OtelManager { #[allow(clippy::too_many_arguments)] @@ -252,6 +258,9 @@ impl OtelManager { .get("type") .and_then(|value| value.as_str()) .map(std::string::ToString::to_string); + if kind.as_deref() == Some(RESPONSES_WEBSOCKET_TIMING_KIND) { + self.record_responses_websocket_timing_metrics(&value); + } if kind.as_deref() == Some("response.failed") { success = false; error_message = value @@ -651,6 +660,22 @@ impl OtelManager { ); } + fn record_responses_websocket_timing_metrics(&self, value: &serde_json::Value) { + let timing_metrics = value.get(RESPONSES_WEBSOCKET_TIMING_METRICS_FIELD); + + let overhead_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_OVERHEAD_FIELD)); + if let Some(duration) = duration_from_ms_value(overhead_value) { + self.record_duration(RESPONSES_API_OVERHEAD_DURATION_METRIC, duration, &[]); + } + + let inference_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_INFERENCE_FIELD)); + if let Some(duration) = duration_from_ms_value(inference_value) { + self.record_duration(RESPONSES_API_INFERENCE_TIME_DURATION_METRIC, duration, &[]); + } + } + fn responses_type(event: &ResponseEvent) -> String { match event { ResponseEvent::Created => "created".into(), @@ -689,3 +714,16 @@ impl OtelManager { fn timestamp() -> String { Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true) } + +fn duration_from_ms_value(value: Option<&serde_json::Value>) -> Option { + let value = value?; + let ms = value + .as_f64() + .or_else(|| value.as_i64().map(|v| v as f64)) + .or_else(|| value.as_u64().map(|v| v as f64))?; + if !ms.is_finite() || ms < 0.0 { + return None; + } + let clamped = ms.min(u64::MAX as f64); + Some(Duration::from_millis(clamped.round() as u64)) +} diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs index 78aed8a0a7..811c801fce 100644 --- a/codex-rs/otel/tests/suite/runtime_summary.rs +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -62,6 +62,14 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( r#"{"type":"response.created"}"#.into(), )))); manager.record_websocket_event(&ws_response, Duration::from_millis(80)); + let ws_timing_response: std::result::Result< + Option>, + codex_api::ApiError, + > = Ok(Some(Ok(Message::Text( + r#"{"type":"responsesapi.websocket_timing","timing_metrics":{"responses_duration_excl_engine_and_client_tool_time_ms":124,"engine_service_total_ms":457}}"# + .into(), + )))); + manager.record_websocket_event(&ws_timing_response, Duration::from_millis(20)); let summary = manager .runtime_metrics_summary() @@ -84,9 +92,11 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( duration_ms: 400, }, websocket_events: RuntimeMetricTotals { - count: 1, - duration_ms: 80, + count: 2, + duration_ms: 100, }, + responses_api_overhead_ms: 124, + responses_api_inference_time_ms: 457, }; assert_eq!(summary, expected); diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 49ccc481bf..b85ca8f6bb 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2213,6 +2213,14 @@ fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { summary.websocket_events.count )); } + if summary.responses_api_overhead_ms > 0 { + let duration = format_duration_ms(summary.responses_api_overhead_ms); + parts.push(format!("Responses API overhead: {duration}")); + } + if summary.responses_api_inference_time_ms > 0 { + let duration = format_duration_ms(summary.responses_api_inference_time_ms); + parts.push(format!("Responses API inference: {duration}")); + } if parts.is_empty() { None } else { @@ -2381,9 +2389,11 @@ mod tests { count: 4, duration_ms: 1_200, }, + responses_api_overhead_ms: 650, + responses_api_inference_time_ms: 1_940, }; let cell = FinalMessageSeparator::new(Some(12), Some(summary)); - let rendered = render_lines(&cell.display_lines(200)); + let rendered = render_lines(&cell.display_lines(300)); assert_eq!(rendered.len(), 1); assert!(!rendered[0].contains("Worked for")); @@ -2392,6 +2402,8 @@ mod tests { assert!(rendered[0].contains("WebSocket: 1 events send (700ms)")); assert!(rendered[0].contains("Streams: 6 events (900ms)")); assert!(rendered[0].contains("4 events received (1.2s)")); + assert!(rendered[0].contains("Responses API overhead: 650ms")); + assert!(rendered[0].contains("Responses API inference: 1.9s")); } #[test] From 38a47700b526a7ae3954d2dd8a233d166bc0a7b8 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 3 Feb 2026 18:15:55 -0800 Subject: [PATCH 07/90] Add thread/compact v2 (#10445) - add `thread/compact` as a trigger-only v2 RPC that submits `Op::Compact` and returns `{}` immediately. - add v2 compaction e2e coverage for success and invalid/unknown thread ids, and update protocol schemas/docs. --- .../schema/json/ClientRequest.json | 35 +++++ .../codex_app_server_protocol.schemas.json | 42 ++++++ .../json/v2/ThreadCompactStartParams.json | 13 ++ .../json/v2/ThreadCompactStartResponse.json | 5 + .../schema/typescript/ClientRequest.ts | 3 +- .../typescript/v2/ThreadCompactStartParams.ts | 5 + .../v2/ThreadCompactStartResponse.ts | 5 + .../schema/typescript/v2/index.ts | 2 + .../src/protocol/common.rs | 4 + .../app-server-protocol/src/protocol/v2.rs | 12 ++ codex-rs/app-server/README.md | 17 +++ .../app-server/src/codex_message_processor.rs | 29 ++++ .../app-server/tests/common/mcp_process.rs | 10 ++ .../app-server/tests/suite/v2/compaction.rs | 132 ++++++++++++++++++ 14 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 39faeb1378..9b676f8e52 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2235,6 +2235,17 @@ ], "type": "object" }, + "ThreadCompactStartParams": { + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "type": "object" + }, "ThreadForkParams": { "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", "properties": { @@ -3210,6 +3221,30 @@ "title": "Thread/unarchiveRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/compact/start" + ], + "title": "Thread/compact/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ThreadCompactStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/compact/startRequest", + "type": "object" + }, { "properties": { "id": { 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 b813b5946c..f0b16e2358 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 @@ -598,6 +598,30 @@ "title": "Thread/unarchiveRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "thread/compact/start" + ], + "title": "Thread/compact/startRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ThreadCompactStartParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Thread/compact/startRequest", + "type": "object" + }, { "properties": { "id": { @@ -13855,6 +13879,24 @@ "title": "ThreadArchiveResponse", "type": "object" }, + "ThreadCompactStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadCompactStartParams", + "type": "object" + }, + "ThreadCompactStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartResponse", + "type": "object" + }, "ThreadForkParams": { "$schema": "http://json-schema.org/draft-07/schema#", "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json new file mode 100644 index 0000000000..a174ff95d9 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartParams.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "threadId": { + "type": "string" + } + }, + "required": [ + "threadId" + ], + "title": "ThreadCompactStartParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json new file mode 100644 index 0000000000..bb372b6ddd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadCompactStartResponse.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index d526e5c5e9..176f86be56 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -39,6 +39,7 @@ import type { SkillsListParams } from "./v2/SkillsListParams"; import type { SkillsRemoteReadParams } from "./v2/SkillsRemoteReadParams"; import type { SkillsRemoteWriteParams } from "./v2/SkillsRemoteWriteParams"; import type { ThreadArchiveParams } from "./v2/ThreadArchiveParams"; +import type { ThreadCompactStartParams } from "./v2/ThreadCompactStartParams"; import type { ThreadForkParams } from "./v2/ThreadForkParams"; import type { ThreadListParams } from "./v2/ThreadListParams"; import type { ThreadLoadedListParams } from "./v2/ThreadLoadedListParams"; @@ -54,4 +55,4 @@ import type { TurnStartParams } from "./v2/TurnStartParams"; /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.ts new file mode 100644 index 0000000000..a60b2c2812 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartParams.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 ThreadCompactStartParams = { threadId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.ts new file mode 100644 index 0000000000..3794feb270 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadCompactStartResponse.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 ThreadCompactStartResponse = Record; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 83357ff5c6..ed4f74d4ad 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -128,6 +128,8 @@ export type { TextRange } from "./TextRange"; export type { Thread } from "./Thread"; export type { ThreadArchiveParams } from "./ThreadArchiveParams"; export type { ThreadArchiveResponse } from "./ThreadArchiveResponse"; +export type { ThreadCompactStartParams } from "./ThreadCompactStartParams"; +export type { ThreadCompactStartResponse } from "./ThreadCompactStartResponse"; export type { ThreadForkParams } from "./ThreadForkParams"; export type { ThreadForkResponse } from "./ThreadForkResponse"; export type { ThreadItem } from "./ThreadItem"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index bb2510684e..c84d28ae89 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -208,6 +208,10 @@ client_request_definitions! { params: v2::ThreadUnarchiveParams, response: v2::ThreadUnarchiveResponse, }, + ThreadCompactStart => "thread/compact/start" { + params: v2::ThreadCompactStartParams, + response: v2::ThreadCompactStartResponse, + }, ThreadRollback => "thread/rollback" { params: v2::ThreadRollbackParams, response: v2::ThreadRollbackResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index dbc51fa9f9..8e7beccf79 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1408,6 +1408,18 @@ pub struct ThreadUnarchiveResponse { pub thread: Thread, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadCompactStartParams { + pub thread_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ThreadCompactStartResponse {} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 78a4f3cf94..055ca6f32a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -85,6 +85,7 @@ Example (from OpenAI's official VSCode extension): - `thread/archive` — move a thread’s rollout file into the archived directory; returns `{}` on success. - `thread/name/set` — set or update a thread’s user-facing name; returns `{}` on success. Thread names are not required to be unique; name lookups resolve to the most recently updated thread. - `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success. +- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications. - `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success. - `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. @@ -239,6 +240,22 @@ Use `thread/unarchive` to move an archived rollout back into the sessions direct { "id": 24, "result": { "thread": { "id": "thr_b" } } } ``` +### Example: Trigger thread compaction + +Use `thread/compact/start` to trigger manual history compaction for a thread. The request returns immediately with `{}`. + +Progress is emitted as standard `turn/*` and `item/*` notifications on the same `threadId`. Clients should expect a single compaction item: + +- `item/started` with `item: { "type": "contextCompaction", ... }` +- `item/completed` with the same `contextCompaction` item id + +While compaction is running, the thread is effectively in a turn so clients should surface progress UI based on the notifications. + +```json +{ "method": "thread/compact/start", "id": 25, "params": { "threadId": "thr_b" } } +{ "id": 25, "result": {} } +``` + ### Example: Start a turn (send user input) Turns attach user input (text or images) to a thread and trigger Codex generation. The `input` field is a list of discriminated unions: diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 866998a4fd..219cb46570 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -104,6 +104,8 @@ use codex_app_server_protocol::SkillsRemoteWriteResponse; use codex_app_server_protocol::Thread; use codex_app_server_protocol::ThreadArchiveParams; use codex_app_server_protocol::ThreadArchiveResponse; +use codex_app_server_protocol::ThreadCompactStartParams; +use codex_app_server_protocol::ThreadCompactStartResponse; use codex_app_server_protocol::ThreadForkParams; use codex_app_server_protocol::ThreadForkResponse; use codex_app_server_protocol::ThreadItem; @@ -455,6 +457,9 @@ impl CodexMessageProcessor { ClientRequest::ThreadUnarchive { request_id, params } => { self.thread_unarchive(request_id, params).await; } + ClientRequest::ThreadCompactStart { request_id, params } => { + self.thread_compact_start(request_id, params).await; + } ClientRequest::ThreadRollback { request_id, params } => { self.thread_rollback(request_id, params).await; } @@ -2094,6 +2099,30 @@ impl CodexMessageProcessor { } } + async fn thread_compact_start(&self, request_id: RequestId, params: ThreadCompactStartParams) { + let ThreadCompactStartParams { thread_id } = params; + + let (_, thread) = match self.load_thread(&thread_id).await { + Ok(v) => v, + Err(error) => { + self.outgoing.send_error(request_id, error).await; + return; + } + }; + + match thread.submit(Op::Compact).await { + Ok(_) => { + self.outgoing + .send_response(request_id, ThreadCompactStartResponse {}) + .await; + } + Err(err) => { + self.send_internal_error(request_id, format!("failed to start compaction: {err}")) + .await; + } + } + } + async fn thread_list(&self, request_id: RequestId, params: ThreadListParams) { let ThreadListParams { cursor, diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 4804a8cd37..ba6dc058e6 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -50,6 +50,7 @@ use codex_app_server_protocol::SendUserTurnParams; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::SetDefaultModelParams; use codex_app_server_protocol::ThreadArchiveParams; +use codex_app_server_protocol::ThreadCompactStartParams; use codex_app_server_protocol::ThreadForkParams; use codex_app_server_protocol::ThreadListParams; use codex_app_server_protocol::ThreadLoadedListParams; @@ -418,6 +419,15 @@ impl McpProcess { self.send_request("thread/unarchive", params).await } + /// Send a `thread/compact/start` JSON-RPC request. + pub async fn send_thread_compact_start_request( + &mut self, + params: ThreadCompactStartParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("thread/compact/start", params).await + } + /// Send a `thread/rollback` JSON-RPC request. pub async fn send_thread_rollback_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/compaction.rs b/codex-rs/app-server/tests/suite/v2/compaction.rs index eeedcb286b..4730d920c8 100644 --- a/codex-rs/app-server/tests/suite/v2/compaction.rs +++ b/codex-rs/app-server/tests/suite/v2/compaction.rs @@ -15,9 +15,12 @@ use app_test_support::write_chatgpt_auth; use app_test_support::write_mock_responses_config_toml; use codex_app_server_protocol::ItemCompletedNotification; use codex_app_server_protocol::ItemStartedNotification; +use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadCompactStartParams; +use codex_app_server_protocol::ThreadCompactStartResponse; use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadStartResponse; @@ -39,6 +42,7 @@ use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); const AUTO_COMPACT_LIMIT: i64 = 1_000; const COMPACT_PROMPT: &str = "Summarize the conversation."; +const INVALID_REQUEST_ERROR_CODE: i64 = -32600; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn auto_compaction_local_emits_started_and_completed_items() -> Result<()> { @@ -196,6 +200,134 @@ async fn auto_compaction_remote_emits_started_and_completed_items() -> Result<() Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn thread_compact_start_triggers_compaction_and_returns_empty_response() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let sse = responses::sse(vec![ + responses::ev_assistant_message("m1", "MANUAL_COMPACT_SUMMARY"), + responses::ev_completed_with_tokens("r1", 200), + ]); + responses::mount_sse_sequence(&server, vec![sse]).await; + + let codex_home = TempDir::new()?; + write_mock_responses_config_toml( + codex_home.path(), + &server.uri(), + &BTreeMap::default(), + AUTO_COMPACT_LIMIT, + None, + "mock_provider", + COMPACT_PROMPT, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_id = start_thread(&mut mcp).await?; + let compact_id = mcp + .send_thread_compact_start_request(ThreadCompactStartParams { + thread_id: thread_id.clone(), + }) + .await?; + let compact_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(compact_id)), + ) + .await??; + let _compact: ThreadCompactStartResponse = + to_response::(compact_resp)?; + + let started = wait_for_context_compaction_started(&mut mcp).await?; + let completed = wait_for_context_compaction_completed(&mut mcp).await?; + + let ThreadItem::ContextCompaction { id: started_id } = started.item else { + unreachable!("started item should be context compaction"); + }; + let ThreadItem::ContextCompaction { id: completed_id } = completed.item else { + unreachable!("completed item should be context compaction"); + }; + + assert_eq!(started.thread_id, thread_id); + assert_eq!(completed.thread_id, thread_id); + assert_eq!(started_id, completed_id); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn thread_compact_start_rejects_invalid_thread_id() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let codex_home = TempDir::new()?; + write_mock_responses_config_toml( + codex_home.path(), + &server.uri(), + &BTreeMap::default(), + AUTO_COMPACT_LIMIT, + None, + "mock_provider", + COMPACT_PROMPT, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_thread_compact_start_request(ThreadCompactStartParams { + thread_id: "not-a-thread-id".to_string(), + }) + .await?; + let error: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + + assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE); + assert!(error.error.message.contains("invalid thread id")); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn thread_compact_start_rejects_unknown_thread_id() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let codex_home = TempDir::new()?; + write_mock_responses_config_toml( + codex_home.path(), + &server.uri(), + &BTreeMap::default(), + AUTO_COMPACT_LIMIT, + None, + "mock_provider", + COMPACT_PROMPT, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_thread_compact_start_request(ThreadCompactStartParams { + thread_id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(), + }) + .await?; + let error: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(request_id)), + ) + .await??; + + assert_eq!(error.error.code, INVALID_REQUEST_ERROR_CODE); + assert!(error.error.message.contains("thread not found")); + + Ok(()) +} + async fn start_thread(mcp: &mut McpProcess) -> Result { let thread_id = mcp .send_thread_start_request(ThreadStartParams { From 56ebfff1a800095fb501c80e9035455a23b4e43d Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 3 Feb 2026 21:59:13 -0800 Subject: [PATCH 08/90] Move metadata calculation out of client (#10589) Model client shouldn't be responsible for this. --- codex-rs/core/src/client.rs | 61 ++----------- codex-rs/core/src/codex.rs | 51 +++++++++-- codex-rs/core/src/compact.rs | 5 +- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/turn_metadata.rs | 2 +- codex-rs/core/tests/responses_headers.rs | 105 +++-------------------- 6 files changed, 70 insertions(+), 155 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 314bfe7b40..1d0dc7fb65 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -1,13 +1,10 @@ -use std::path::PathBuf; use std::sync::Arc; use std::sync::OnceLock; -use std::sync::RwLock; use crate::api_bridge::CoreAuthProvider; use crate::api_bridge::auth_provider_from_auth; use crate::api_bridge::map_api_error; use crate::auth::UnauthorizedRecovery; -use crate::turn_metadata::build_turn_metadata_header; use codex_api::CompactClient as ApiCompactClient; use codex_api::CompactionInput as ApiCompactionInput; use codex_api::Prompt as ApiPrompt; @@ -75,12 +72,6 @@ pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = "x-responsesapi-include-timing-metrics"; -#[derive(Debug, Default)] -struct TurnMetadataCache { - cwd: Option, - header: Option, -} - #[derive(Debug)] struct ModelClientState { config: Arc, @@ -93,7 +84,6 @@ struct ModelClientState { summary: ReasoningSummaryConfig, session_source: SessionSource, transport_manager: TransportManager, - turn_metadata_cache: Arc>, } #[derive(Debug, Clone)] @@ -106,6 +96,7 @@ pub struct ModelClientSession { connection: Option, websocket_last_items: Vec, transport_manager: TransportManager, + turn_metadata_header: Option, /// Turn state for sticky routing. /// /// This is an `OnceLock` that stores the turn state value received from the server @@ -145,53 +136,20 @@ impl ModelClient { summary, session_source, transport_manager, - turn_metadata_cache: Arc::new(RwLock::new(TurnMetadataCache::default())), }), } } - pub fn new_session(&self, turn_metadata_cwd: Option) -> ModelClientSession { - self.prewarm_turn_metadata_header(turn_metadata_cwd); + pub fn new_session(&self, turn_metadata_header: Option) -> ModelClientSession { ModelClientSession { state: Arc::clone(&self.state), connection: None, websocket_last_items: Vec::new(), transport_manager: self.state.transport_manager.clone(), + turn_metadata_header, turn_state: Arc::new(OnceLock::new()), } } - - /// Refresh turn metadata in the background and update a cached header that request - /// builders can read without blocking. - fn prewarm_turn_metadata_header(&self, turn_metadata_cwd: Option) { - let turn_metadata_cwd = - turn_metadata_cwd.map(|cwd| std::fs::canonicalize(&cwd).unwrap_or(cwd)); - - if let Ok(mut cache) = self.state.turn_metadata_cache.write() - && cache.cwd != turn_metadata_cwd - { - cache.cwd = turn_metadata_cwd.clone(); - cache.header = None; - } - - let Some(cwd) = turn_metadata_cwd else { - return; - }; - let turn_metadata_cache = Arc::clone(&self.state.turn_metadata_cache); - if let Ok(handle) = tokio::runtime::Handle::try_current() { - let _task = handle.spawn(async move { - let header = build_turn_metadata_header(cwd.as_path()) - .await - .and_then(|value| HeaderValue::from_str(value.as_str()).ok()); - - if let Ok(mut cache) = turn_metadata_cache.write() - && cache.cwd.as_ref() == Some(&cwd) - { - cache.header = header; - } - }); - } - } } impl ModelClient { @@ -300,14 +258,6 @@ impl ModelClient { } impl ModelClientSession { - fn turn_metadata_header(&self) -> Option { - self.state - .turn_metadata_cache - .try_read() - .ok() - .and_then(|cache| cache.header.clone()) - } - /// Streams a single model turn using the configured Responses transport. pub async fn stream(&mut self, prompt: &Prompt) -> Result { let wire_api = self.state.provider.wire_api; @@ -364,7 +314,10 @@ impl ModelClientSession { prompt: &Prompt, compression: Compression, ) -> ApiResponsesOptions { - let turn_metadata_header = self.turn_metadata_header(); + let turn_metadata_header = self + .turn_metadata_header + .as_deref() + .and_then(|value| HeaderValue::from_str(value).ok()); let model_info = &self.state.model_info; let default_reasoning_effort = model_info.default_reasoning_level; diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index b7c0404056..7fe3b8c810 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -34,6 +34,7 @@ use crate::stream_events_utils::last_assistant_message_from_item; use crate::terminal; use crate::transport_manager::TransportManager; use crate::truncate::TruncationPolicy; +use crate::turn_metadata::build_turn_metadata_header; use crate::user_notification::UserNotifier; use crate::util::error_or_panic; use async_channel::Receiver; @@ -80,6 +81,7 @@ use rmcp::model::RequestId; use serde_json; use serde_json::Value; use tokio::sync::Mutex; +use tokio::sync::OnceCell; use tokio::sync::RwLock; use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; @@ -90,6 +92,7 @@ use tracing::field; use tracing::info; use tracing::info_span; use tracing::instrument; +use tracing::trace; use tracing::trace_span; use tracing::warn; @@ -501,6 +504,7 @@ pub(crate) struct TurnContext { pub(crate) tool_call_gate: Arc, pub(crate) truncation_policy: TruncationPolicy, pub(crate) dynamic_tools: Vec, + turn_metadata_header: OnceCell>, } impl TurnContext { pub(crate) fn resolve_path(&self, path: Option) -> PathBuf { @@ -514,6 +518,38 @@ impl TurnContext { .as_deref() .unwrap_or(compact::SUMMARIZATION_PROMPT) } + + async fn build_turn_metadata_header(&self) -> Option { + self.turn_metadata_header + .get_or_init(|| async { build_turn_metadata_header(self.cwd.as_path()).await }) + .await + .clone() + } + + pub async fn resolve_turn_metadata_header(&self) -> Option { + const TURN_METADATA_HEADER_TIMEOUT_MS: u64 = 250; + match tokio::time::timeout( + std::time::Duration::from_millis(TURN_METADATA_HEADER_TIMEOUT_MS), + self.build_turn_metadata_header(), + ) + .await + { + Ok(header) => header, + Err(_) => { + warn!("timed out after 250ms while building turn metadata header"); + self.turn_metadata_header.get().cloned().flatten() + } + } + } + + pub fn spawn_turn_metadata_header_task(self: &Arc) { + let context = Arc::clone(self); + tokio::spawn(async move { + trace!("Spawning turn metadata calculation task"); + context.build_turn_metadata_header().await; + trace!("Turn metadata calculation task completed"); + }); + } } #[derive(Clone)] @@ -682,10 +718,11 @@ impl Session { web_search_mode: per_turn_config.web_search_mode, }); + let cwd = session_configuration.cwd.clone(); TurnContext { sub_id, client, - cwd: session_configuration.cwd.clone(), + cwd, developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), user_instructions: session_configuration.user_instructions.clone(), @@ -702,6 +739,7 @@ impl Session { tool_call_gate: Arc::new(ReadinessFlag::new()), truncation_policy: model_info.truncation_policy.into(), dynamic_tools: session_configuration.dynamic_tools.clone(), + turn_metadata_header: OnceCell::new(), } } @@ -1246,10 +1284,13 @@ impl Session { sub_id, self.services.transport_manager.clone(), ); + if let Some(final_schema) = final_output_json_schema { turn_context.final_output_json_schema = final_schema; } - Arc::new(turn_context) + let turn_context = Arc::new(turn_context); + turn_context.spawn_turn_metadata_header_task(); + turn_context } pub(crate) async fn new_default_turn(&self) -> Arc { @@ -3274,6 +3315,7 @@ async fn spawn_review_thread( tool_call_gate: Arc::new(ReadinessFlag::new()), dynamic_tools: parent_turn_context.dynamic_tools.clone(), truncation_policy: model_info.truncation_policy.into(), + turn_metadata_header: parent_turn_context.turn_metadata_header.clone(), }; // Seed the child task with the review prompt as the initial user message. @@ -3478,9 +3520,8 @@ pub(crate) async fn run_turn( // many turns, from the perspective of the user, it is a single turn. let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); - let mut client_session = turn_context - .client - .new_session(Some(turn_context.cwd.clone())); + let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; + let mut client_session = turn_context.client.new_session(turn_metadata_header); loop { // Note that pending_input would be something like a message the user diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index ee94e4994b..7ae773884c 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -337,9 +337,8 @@ async fn drain_to_completed( turn_context: &TurnContext, prompt: &Prompt, ) -> CodexResult<()> { - let mut client_session = turn_context - .client - .new_session(Some(turn_context.cwd.clone())); + let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; + let mut client_session = turn_context.client.new_session(turn_metadata_header); let mut stream = client_session.stream(prompt).await?; loop { let maybe_event = stream.next().await; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index c5ac0d8f85..9bd4c87253 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -139,6 +139,7 @@ pub use exec_policy::check_execpolicy_for_warnings; pub use exec_policy::load_exec_policy; pub use safety::get_platform_sandbox; pub use tools::spec::parse_tool_input_schema; +pub use turn_metadata::build_turn_metadata_header; // Re-export the protocol types from the standalone `codex-protocol` crate so existing // `codex_core::protocol::...` references continue to work across the workspace. pub use codex_protocol::protocol; diff --git a/codex-rs/core/src/turn_metadata.rs b/codex-rs/core/src/turn_metadata.rs index 58b394a2f2..6d4878c7a3 100644 --- a/codex-rs/core/src/turn_metadata.rs +++ b/codex-rs/core/src/turn_metadata.rs @@ -20,7 +20,7 @@ struct TurnMetadata { workspaces: BTreeMap, } -pub(crate) async fn build_turn_metadata_header(cwd: &Path) -> Option { +pub async fn build_turn_metadata_header(cwd: &Path) -> Option { let repo_root = get_git_repo_root(cwd)?; let (latest_git_commit_hash, associated_remote_urls) = tokio::join!( diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 86e511c472..fd65bcf32a 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -408,88 +408,14 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() responses::ev_response_created("resp-1"), responses::ev_completed("resp-1"), ]); - let provider = ModelProviderInfo { - name: "mock".into(), - base_url: Some(format!("{}/v1", server.uri())), - env_key: None, - env_key_instructions: None, - experimental_bearer_token: None, - wire_api: WireApi::Responses, - query_params: None, - http_headers: None, - env_http_headers: None, - request_max_retries: Some(0), - stream_max_retries: Some(0), - stream_idle_timeout_ms: Some(5_000), - requires_openai_auth: false, - supports_websockets: false, - }; - let codex_home = TempDir::new().expect("failed to create TempDir"); - let mut config = load_default_config_for_test(&codex_home).await; - config.model_provider_id = provider.name.clone(); - config.model_provider = provider.clone(); - let effort = config.model_reasoning_effort; - let summary = config.model_reasoning_summary; - let model = ModelsManager::get_model_offline(config.model.as_deref()); - config.model = Some(model.clone()); - let config = Arc::new(config); - - let conversation_id = ThreadId::new(); - let auth_mode = AuthMode::Chatgpt; - let session_source = - SessionSource::SubAgent(SubAgentSource::Other("turn-metadata-e2e".to_string())); - let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); - let otel_manager = OtelManager::new( - conversation_id, - model.as_str(), - model_info.slug.as_str(), - None, - Some("test@test.com".to_string()), - Some(auth_mode), - false, - "test".to_string(), - session_source.clone(), - ); - - let client = ModelClient::new( - Arc::clone(&config), - None, - model_info, - otel_manager, - provider, - effort, - summary, - conversation_id, - session_source, - TransportManager::new(), - ); - - let workspace = TempDir::new().expect("workspace tempdir"); - let cwd = workspace.path(); - - let mut prompt = Prompt::default(); - prompt.input = vec![ResponseItem::Message { - id: None, - role: "user".into(), - content: vec![ContentItem::InputText { - text: "hello".into(), - }], - end_turn: None, - phase: None, - }]; + let test = test_codex().build(&server).await.expect("build test codex"); + let cwd = test.cwd_path(); let first_request = responses::mount_sse_once(&server, response_body.clone()).await; - let mut first_session = client.new_session(Some(cwd.to_path_buf())); - let mut first_stream = first_session - .stream(&prompt) + test.submit_turn("hello") .await - .expect("stream first turn"); - while let Some(event) = first_stream.next().await { - if matches!(event, Ok(ResponseEvent::Completed { .. })) { - break; - } - } + .expect("submit first turn prompt"); assert_eq!( first_request .single_request() @@ -539,21 +465,13 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() .trim() .to_string(); - let repo_root = std::fs::canonicalize(cwd) - .unwrap_or_else(|_| cwd.to_path_buf()) - .to_string_lossy() - .into_owned(); let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(5); loop { let request_recorder = responses::mount_sse_once(&server, response_body.clone()).await; - let mut session = client.new_session(Some(cwd.to_path_buf())); tokio::time::sleep(std::time::Duration::from_millis(50)).await; - let mut stream = session.stream(&prompt).await.expect("stream post-git turn"); - while let Some(event) = stream.next().await { - if matches!(event, Ok(ResponseEvent::Completed { .. })) { - break; - } - } + test.submit_turn("hello") + .await + .expect("submit post-git turn prompt"); let maybe_header = request_recorder .single_request() @@ -561,11 +479,14 @@ async fn responses_stream_includes_turn_metadata_header_for_git_workspace_e2e() if let Some(header_value) = maybe_header { let parsed: serde_json::Value = serde_json::from_str(&header_value) .expect("x-codex-turn-metadata should be valid JSON"); - let workspace = parsed + let workspaces = parsed .get("workspaces") .and_then(serde_json::Value::as_object) - .and_then(|workspaces| workspaces.get(&repo_root)) - .expect("metadata should include cwd repo root workspace entry"); + .expect("metadata should include workspaces"); + let workspace = workspaces + .values() + .next() + .expect("metadata should include at least one workspace entry"); assert_eq!( workspace From 968c029471dc13ad24095f7cb87cbb19a5bf172d Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Tue, 3 Feb 2026 22:29:52 -0800 Subject: [PATCH 09/90] fix(core) updated request_rule guidance (#10379) ## Summary Update guidance for request_rule ## Testing - [x] Unit tests pass --- codex-rs/protocol/src/models.rs | 2 +- .../approval_policy/on_request_rule.md | 31 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index fe63666e38..4231423a63 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -249,7 +249,7 @@ impl DeveloperInstructions { match command_prefixes { Some(prefixes) => { format!( - "{APPROVAL_POLICY_ON_REQUEST_RULE}\nApproved command prefixes:\n{prefixes}" + "{APPROVAL_POLICY_ON_REQUEST_RULE}\n## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}" ) } None => APPROVAL_POLICY_ON_REQUEST_RULE.to_string(), diff --git a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md index 336f1fb748..2e9080664d 100644 --- a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md +++ b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md @@ -37,25 +37,20 @@ While commands are running inside the sandbox, here are some scenarios that will - You need to run a GUI app (e.g., open/xdg-open/osascript) to open browsers or files. - If you run a command that is important to solving the user's query, but it fails because of sandboxing, rerun the command with `require_escalated`. ALWAYS proceed to use the `sandbox_permissions` and `justification` parameters. do not message the user before requesting approval for the command. - You are about to take a potentially destructive action such as an `rm` or `git reset` that the user did not explicitly ask for. - -Only run commands that require approval if it is absolutely necessary to solve the user's query, don't try and circumvent approvals by using other tools. +- Be judicious with escalating, but if completing the user's request requires it, you should do so - don't try and circumvent approvals by using other tools. ## prefix_rule guidance -When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user in the future without re-requesting escalation. It should be categorical and reasonably scoped to similar capabilities. You MUST NOT pass the entire command into `prefix_rule`. +When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user in the future without re-requesting escalation. It should be categorical and reasonably scoped to similar capabilities. You should rarely pass the entire command into `prefix_rule`. + +### Banned prefix_rules +NEVER provide a prefix_rule argument for destructive commands like rm. +Do not provide a prefix rule if your command uses a heredoc or herestring. + +### Examples +Good examples of prefixes: +- ["npm", "run", "dev"] +- ["gh", "pr", "check"] +- ["pytest"] +- ["cargo", "test"] - -["npm", "run", "dev"] - - -["gh", "pr", "checks"] - - -["pytest"] - - -["cargo", "test", "-p", "codex-app-server"] - -["cargo", "test"] - - From 8f17b37d0663c2e83ac4ecb502eec123f5924527 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Wed, 4 Feb 2026 00:44:32 -0800 Subject: [PATCH 10/90] fix(core) Request Rule guidance tweak (#10598) ## Summary Forgot to include this tweak. ## Testing - [x] Unit tests pass --- .../prompts/permissions/approval_policy/on_request_rule.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md index 2e9080664d..96d962d12d 100644 --- a/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md +++ b/codex-rs/protocol/src/prompts/permissions/approval_policy/on_request_rule.md @@ -25,9 +25,9 @@ IMPORTANT: To request approval to execute a command that will require escalated - Provide the `sandbox_permissions` parameter with the value `"require_escalated"` - Include a short question asking the user if they want to allow the action in `justification` parameter. e.g. "Do you want to download and install dependencies for this project?" -- Suggest a `prefix_rule` - this will be shown to the user with an option to persist the rule approval for future sessions. +- Optionally suggest a `prefix_rule` - this will be shown to the user with an option to persist the rule approval for future sessions. -If you run a command that is important to solving the user's query, but it fails because of sandboxing, rerun the command with "require_escalated". ALWAYS proceed to use the `justification` and `prefix_rule` parameters - do not message the user before requesting approval for the command. +If you run a command that is important to solving the user's query, but it fails because of sandboxing, rerun the command with "require_escalated". ALWAYS proceed to use the `justification` parameter - do not message the user before requesting approval for the command. ## When to request escalation @@ -44,8 +44,9 @@ While commands are running inside the sandbox, here are some scenarios that will When choosing a `prefix_rule`, request one that will allow you to fulfill similar requests from the user in the future without re-requesting escalation. It should be categorical and reasonably scoped to similar capabilities. You should rarely pass the entire command into `prefix_rule`. ### Banned prefix_rules +Avoid requesting overly broad prefixes that the user would be ill-advised to approve. For example, do not request ["python3"], ["python", "-"], or other similar prefixes. NEVER provide a prefix_rule argument for destructive commands like rm. -Do not provide a prefix rule if your command uses a heredoc or herestring. +NEVER provide a prefix_rule if your command uses a heredoc or herestring. ### Examples Good examples of prefixes: From 100eb6e6f0067f75b2e964c84769c5b9fae680a6 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 09:27:24 +0000 Subject: [PATCH 11/90] Prefer state DB thread listings before filesystem (#10544) Summary - add Cursor/ThreadsPage conversions so state DB listings can be mapped back into the rollout list model - make recorder list helpers query the state DB first (archived flag included) and only fall back to file traversal if needed, along with populating head bytes lazily - add extensive tests to ensure the DB path is honored for active and archived threads and that the fallback works Testing - Not run (not requested) Screenshot 2026-02-03 at 20 42 33 --- codex-rs/core/src/rollout/list.rs | 9 +- codex-rs/core/src/rollout/recorder.rs | 144 ++++++++++++-------- codex-rs/core/src/rollout/tests.rs | 187 ++++++++++++++++++++++++++ codex-rs/core/src/state_db.rs | 53 ++++++++ codex-rs/state/src/extract.rs | 128 ++++++++++++++++-- 5 files changed, 457 insertions(+), 64 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 3f057649a6..5e7c1aa350 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -261,6 +261,14 @@ impl<'de> serde::Deserialize<'de> for Cursor { } } +impl From for Cursor { + fn from(anchor: codex_state::Anchor) -> Self { + let ts = OffsetDateTime::from_unix_timestamp(anchor.ts.timestamp()) + .unwrap_or(OffsetDateTime::UNIX_EPOCH); + Self::new(ts, anchor.id) + } +} + /// Retrieve recorded thread file paths with token pagination. The returned `next_cursor` /// can be supplied on the next call to resume after the last returned item, resilient to /// concurrent new sessions being appended. Ordering is stable by the requested sort key @@ -989,7 +997,6 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result, default_provider: &str, ) -> std::io::Result { - let stage = "list_threads"; - let page = get_threads( + Self::list_threads_with_db_fallback( codex_home, page_size, cursor, @@ -129,35 +131,9 @@ impl RolloutRecorder { allowed_sources, model_providers, default_provider, - ) - .await?; - - // TODO(jif): drop after sqlite migration phase 1 - let state_db_ctx = state_db::open_if_present(codex_home, default_provider).await; - if let Some(db_ids) = state_db::list_thread_ids_db( - state_db_ctx.as_deref(), - codex_home, - page_size, - cursor, - sort_key, - allowed_sources, - model_providers, false, - stage, ) .await - { - if page.items.len() != db_ids.len() { - state_db::record_discrepancy(stage, "bad_len"); - return Ok(page); - } - for (id, item) in db_ids.iter().zip(page.items.iter()) { - if !item.path.display().to_string().contains(&id.to_string()) { - state_db::record_discrepancy(stage, "bad_id"); - } - } - } - Ok(page) } /// List archived threads (rollout files) under the archived sessions directory. @@ -170,25 +146,32 @@ impl RolloutRecorder { model_providers: Option<&[String]>, default_provider: &str, ) -> std::io::Result { - let stage = "list_archived_threads"; - let root = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); - let page = get_threads_in_root( - root, + Self::list_threads_with_db_fallback( + codex_home, page_size, cursor, sort_key, - ThreadListConfig { - allowed_sources, - model_providers, - default_provider, - layout: ThreadListLayout::Flat, - }, + allowed_sources, + model_providers, + default_provider, + true, ) - .await?; + .await + } - // TODO(jif): drop after sqlite migration phase 1 + #[allow(clippy::too_many_arguments)] + async fn list_threads_with_db_fallback( + codex_home: &Path, + page_size: usize, + cursor: Option<&Cursor>, + sort_key: ThreadSortKey, + allowed_sources: &[SessionSource], + model_providers: Option<&[String]>, + default_provider: &str, + archived: bool, + ) -> std::io::Result { let state_db_ctx = state_db::open_if_present(codex_home, default_provider).await; - if let Some(db_ids) = state_db::list_thread_ids_db( + if let Some(db_page) = state_db::list_threads_db( state_db_ctx.as_deref(), codex_home, page_size, @@ -196,22 +179,42 @@ impl RolloutRecorder { sort_key, allowed_sources, model_providers, - true, - stage, + archived, ) .await { - if page.items.len() != db_ids.len() { - state_db::record_discrepancy(stage, "bad_len"); - return Ok(page); - } - for (id, item) in db_ids.iter().zip(page.items.iter()) { - if !item.path.display().to_string().contains(&id.to_string()) { - state_db::record_discrepancy(stage, "bad_id"); - } - } + let mut page: ThreadsPage = db_page.into(); + populate_thread_heads(page.items.as_mut_slice()).await; + return Ok(page); } - Ok(page) + + if archived { + let root = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); + return get_threads_in_root( + root, + page_size, + cursor, + sort_key, + ThreadListConfig { + allowed_sources, + model_providers, + default_provider, + layout: ThreadListLayout::Flat, + }, + ) + .await; + } + + get_threads( + codex_home, + page_size, + cursor, + sort_key, + allowed_sources, + model_providers, + default_provider, + ) + .await } /// Find the newest recorded thread path, optionally filtering to a matching cwd. @@ -645,6 +648,41 @@ impl JsonlWriter { } } +impl From for ThreadsPage { + fn from(db_page: codex_state::ThreadsPage) -> Self { + let items = db_page + .items + .into_iter() + .map(|item| ThreadItem { + path: item.rollout_path, + head: Vec::new(), + created_at: Some(item.created_at.to_rfc3339_opts(SecondsFormat::Secs, true)), + updated_at: Some(item.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true)), + }) + .collect(); + Self { + items, + next_cursor: db_page.next_anchor.map(Into::into), + num_scanned_files: db_page.num_scanned_rows, + reached_scan_cap: false, + } + } +} + +async fn populate_thread_heads(items: &mut [ThreadItem]) { + for item in items { + item.head = read_head_for_summary(item.path.as_path()) + .await + .unwrap_or_else(|err| { + warn!( + "failed to read rollout head from state db path: {} ({err})", + item.path.display() + ); + Vec::new() + }); + } +} + fn select_resume_path(page: &ThreadsPage, filter_cwd: Option<&Path>) -> Option { match filter_cwd { Some(cwd) => page.items.iter().find_map(|item| { diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index ee750b126d..e42e186157 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -7,6 +7,7 @@ use std::fs::{self}; use std::io::Write; use std::path::Path; +use chrono::TimeZone; use pretty_assertions::assert_eq; use tempfile::TempDir; use time::Duration; @@ -22,6 +23,7 @@ use crate::rollout::list::ThreadItem; use crate::rollout::list::ThreadSortKey; use crate::rollout::list::ThreadsPage; use crate::rollout::list::get_threads; +use crate::rollout::recorder::RolloutRecorder; use crate::rollout::rollout_date_parts; use anyhow::Result; use codex_protocol::ThreadId; @@ -45,6 +47,191 @@ fn provider_vec(providers: &[&str]) -> Vec { .collect() } +async fn insert_state_db_thread( + home: &Path, + thread_id: ThreadId, + rollout_path: &Path, + archived: bool, +) { + let runtime = + codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string(), None) + .await + .expect("state db should initialize"); + let created_at = chrono::Utc + .with_ymd_and_hms(2025, 1, 3, 12, 0, 0) + .single() + .expect("valid datetime"); + let mut builder = codex_state::ThreadMetadataBuilder::new( + thread_id, + rollout_path.to_path_buf(), + created_at, + SessionSource::Cli, + ); + builder.model_provider = Some(TEST_PROVIDER.to_string()); + builder.cwd = home.to_path_buf(); + if archived { + builder.archived_at = Some(created_at); + } + let mut metadata = builder.build(TEST_PROVIDER); + metadata.has_user_event = true; + runtime + .upsert_thread(&metadata) + .await + .expect("state db upsert should succeed"); +} + +#[tokio::test] +async fn list_threads_prefers_state_db_when_available() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let fs_uuid = Uuid::from_u128(101); + write_session_file( + home, + "2025-01-03T13-00-00", + fs_uuid, + 1, + Some(SessionSource::Cli), + ) + .unwrap(); + + let db_uuid = Uuid::from_u128(102); + let db_thread_id = ThreadId::from_string(&db_uuid.to_string()).expect("valid thread id"); + let db_rollout_path = home.join(format!( + "sessions/2025/01/03/rollout-2025-01-03T12-00-00-{db_uuid}.jsonl" + )); + insert_state_db_thread(home, db_thread_id, db_rollout_path.as_path(), false).await; + + let page = RolloutRecorder::list_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].path, db_rollout_path); +} + +#[tokio::test] +async fn list_archived_threads_prefers_state_db_when_available() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let archived_root = home.join("archived_sessions"); + fs::create_dir_all(&archived_root).unwrap(); + let fs_uuid = Uuid::from_u128(201); + let fs_path = archived_root.join(format!("rollout-2025-01-03T13-00-00-{fs_uuid}.jsonl")); + fs::write(&fs_path, "{\"type\":\"session_meta\",\"payload\":{}}\n").unwrap(); + + let db_uuid = Uuid::from_u128(202); + let db_thread_id = ThreadId::from_string(&db_uuid.to_string()).expect("valid thread id"); + let db_rollout_path = + archived_root.join(format!("rollout-2025-01-03T12-00-00-{db_uuid}.jsonl")); + insert_state_db_thread(home, db_thread_id, db_rollout_path.as_path(), true).await; + + let page = RolloutRecorder::list_archived_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("archived thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].path, db_rollout_path); +} + +#[tokio::test] +async fn list_threads_db_excludes_archived_entries() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let sessions_root = home.join("sessions/2025/01/03"); + let archived_root = home.join("archived_sessions"); + fs::create_dir_all(&sessions_root).unwrap(); + fs::create_dir_all(&archived_root).unwrap(); + + let active_uuid = Uuid::from_u128(211); + let active_thread_id = + ThreadId::from_string(&active_uuid.to_string()).expect("valid active thread id"); + let active_rollout_path = + sessions_root.join(format!("rollout-2025-01-03T12-00-00-{active_uuid}.jsonl")); + insert_state_db_thread(home, active_thread_id, active_rollout_path.as_path(), false).await; + + let archived_uuid = Uuid::from_u128(212); + let archived_thread_id = + ThreadId::from_string(&archived_uuid.to_string()).expect("valid archived thread id"); + let archived_rollout_path = + archived_root.join(format!("rollout-2025-01-03T11-00-00-{archived_uuid}.jsonl")); + insert_state_db_thread( + home, + archived_thread_id, + archived_rollout_path.as_path(), + true, + ) + .await; + + let page = RolloutRecorder::list_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + assert_eq!(page.items[0].path, active_rollout_path); +} + +#[tokio::test] +async fn list_threads_falls_back_to_files_when_state_db_is_unavailable() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let fs_uuid = Uuid::from_u128(301); + write_session_file( + home, + "2025-01-03T13-00-00", + fs_uuid, + 1, + Some(SessionSource::Cli), + ) + .unwrap(); + + let page = RolloutRecorder::list_threads( + home, + 10, + None, + ThreadSortKey::CreatedAt, + NO_SOURCE_FILTER, + None, + TEST_PROVIDER, + ) + .await + .expect("thread listing should succeed"); + + assert_eq!(page.items.len(), 1); + let file_name = page.items[0] + .path + .file_name() + .and_then(|value| value.to_str()) + .expect("rollout file name should be utf8"); + assert!( + file_name.contains(&fs_uuid.to_string()), + "expected file path from filesystem listing, got: {file_name}" + ); +} + #[test] fn rollout_date_parts_extracts_directory_components() { let file_name = OsStr::new("rollout-2025-03-01T09-00-00-123.jsonl"); diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index ff95ed946f..7a0894a327 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -181,6 +181,59 @@ pub async fn list_thread_ids_db( } } +/// List thread metadata from SQLite without rollout directory traversal. +#[allow(clippy::too_many_arguments)] +pub async fn list_threads_db( + context: Option<&codex_state::StateRuntime>, + codex_home: &Path, + page_size: usize, + cursor: Option<&Cursor>, + sort_key: ThreadSortKey, + allowed_sources: &[SessionSource], + model_providers: Option<&[String]>, + archived: bool, +) -> Option { + let ctx = context?; + if ctx.codex_home() != codex_home { + warn!( + "state db codex_home mismatch: expected {}, got {}", + ctx.codex_home().display(), + codex_home.display() + ); + } + + let anchor = cursor_to_anchor(cursor); + let allowed_sources: Vec = allowed_sources + .iter() + .map(|value| match serde_json::to_value(value) { + Ok(Value::String(s)) => s, + Ok(other) => other.to_string(), + Err(_) => String::new(), + }) + .collect(); + let model_providers = model_providers.map(<[String]>::to_vec); + match ctx + .list_threads( + page_size, + anchor.as_ref(), + match sort_key { + ThreadSortKey::CreatedAt => codex_state::SortKey::CreatedAt, + ThreadSortKey::UpdatedAt => codex_state::SortKey::UpdatedAt, + }, + allowed_sources.as_slice(), + model_providers.as_deref(), + archived, + ) + .await + { + Ok(page) => Some(page), + Err(err) => { + warn!("state db list_threads failed: {err}"); + None + } + } +} + /// Look up the rollout path for a thread id using SQLite. pub async fn find_rollout_path_by_id( context: Option<&codex_state::StateRuntime>, diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index ad40118086..bfcd75416f 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -3,14 +3,19 @@ use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; use codex_protocol::models::is_local_image_close_tag_text; use codex_protocol::models::is_local_image_open_tag_text; +use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::TurnContextItem; +use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use serde::Serialize; use serde_json::Value; +const USER_INSTRUCTIONS_PREFIX: &str = "# AGENTS.md instructions for "; +const TURN_ABORTED_OPEN_TAG: &str = ""; + /// Apply a rollout item to the metadata structure. pub fn apply_rollout_item( metadata: &mut ThreadMetadata, @@ -74,12 +79,46 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } fn apply_response_item(metadata: &mut ThreadMetadata, item: &ResponseItem) { - if let Some(text) = extract_user_message_text(item) { - metadata.has_user_event = true; - if metadata.title.is_empty() { - metadata.title = text; - } + if !is_user_response_item(item) { + return; } + metadata.has_user_event = true; + if metadata.title.is_empty() + && let Some(text) = extract_user_message_text(item) + { + metadata.title = text; + } +} + +// TODO(jif) unify once the discussion is settled +fn is_user_response_item(item: &ResponseItem) -> bool { + let ResponseItem::Message { role, content, .. } = item else { + return false; + }; + role == "user" + && !is_user_instructions(content.as_slice()) + && !is_session_prefix_content(content.as_slice()) +} + +fn is_user_instructions(content: &[ContentItem]) -> bool { + if let [ContentItem::InputText { text }] = content { + text.starts_with(USER_INSTRUCTIONS_PREFIX) || text.starts_with(USER_INSTRUCTIONS_OPEN_TAG) + } else { + false + } +} + +fn is_session_prefix_content(content: &[ContentItem]) -> bool { + if let [ContentItem::InputText { text }] = content { + is_session_prefix(text) + } else { + false + } +} + +fn is_session_prefix(text: &str) -> bool { + let lowered = text.trim_start().to_ascii_lowercase(); + lowered.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) || lowered.starts_with(TURN_ABORTED_OPEN_TAG) } fn extract_user_message_text(item: &ResponseItem) -> Option { @@ -125,6 +164,7 @@ pub(crate) fn enum_to_string(value: &T) -> String { #[cfg(test)] mod tests { + use super::apply_rollout_item; use super::extract_user_message_text; use crate::model::ThreadMetadata; use chrono::DateTime; @@ -132,6 +172,8 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; + use codex_protocol::protocol::RolloutItem; + use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use pretty_assertions::assert_eq; use std::path::PathBuf; @@ -158,10 +200,69 @@ mod tests { } #[test] - fn diff_fields_detects_changes() { - let id = ThreadId::from_string(&Uuid::now_v7().to_string()).expect("thread id"); + fn user_instructions_do_not_count_as_user_events() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: format!( + "# AGENTS.md instructions for /tmp\n\n{USER_INSTRUCTIONS_OPEN_TAG}test" + ), + }], + end_turn: None, + phase: None, + }); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.has_user_event, false); + assert_eq!(metadata.title, ""); + } + + #[test] + fn session_prefix_messages_do_not_count_as_user_events() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "\n {\"cwd\":\"/tmp\"}" + .to_string(), + }], + end_turn: None, + phase: None, + }); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.has_user_event, false); + assert_eq!(metadata.title, ""); + } + + #[test] + fn image_only_user_messages_still_count_as_user_events() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputImage { + image_url: "https://example.com/image.png".to_string(), + }], + end_turn: None, + phase: None, + }); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.has_user_event, true); + assert_eq!(metadata.title, ""); + } + + fn metadata_for_test() -> ThreadMetadata { + let id = ThreadId::from_string(&Uuid::from_u128(42).to_string()).expect("thread id"); let created_at = DateTime::::from_timestamp(1_735_689_600, 0).expect("timestamp"); - let base = ThreadMetadata { + ThreadMetadata { id, rollout_path: PathBuf::from("/tmp/a.jsonl"), created_at, @@ -169,7 +270,7 @@ mod tests { source: "cli".to_string(), model_provider: "openai".to_string(), cwd: PathBuf::from("/tmp"), - title: "hello".to_string(), + title: String::new(), sandbox_policy: "read-only".to_string(), approval_mode: "on-request".to_string(), tokens_used: 1, @@ -178,7 +279,14 @@ mod tests { git_sha: None, git_branch: None, git_origin_url: None, - }; + } + } + + #[test] + fn diff_fields_detects_changes() { + let mut base = metadata_for_test(); + base.id = ThreadId::from_string(&Uuid::now_v7().to_string()).expect("thread id"); + base.title = "hello".to_string(); let mut other = base.clone(); other.tokens_used = 2; other.title = "world".to_string(); From 3d8deeea4b60e1c5ed98bb55779dee3ea96b73e0 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 10:57:58 +0000 Subject: [PATCH 12/90] fix: single transaction for dyn tools injection (#10614) --- codex-rs/state/src/runtime.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 9a750f1d5d..c64b7d56c6 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -419,6 +419,7 @@ ON CONFLICT(id) DO UPDATE SET return Ok(()); } let thread_id = thread_id.to_string(); + let mut tx = self.pool.begin().await?; for (idx, tool) in tools.iter().enumerate() { let position = i64::try_from(idx).unwrap_or(i64::MAX); let input_schema = serde_json::to_string(&tool.input_schema)?; @@ -439,9 +440,10 @@ ON CONFLICT(thread_id, position) DO NOTHING .bind(tool.name.as_str()) .bind(tool.description.as_str()) .bind(input_schema) - .execute(self.pool.as_ref()) + .execute(&mut *tx) .await?; } + tx.commit().await?; Ok(()) } From 1eb21e279e9282b5b894bcfcf890d390cae7d643 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Wed, 4 Feb 2026 11:09:48 +0000 Subject: [PATCH 13/90] Requirements: add source to constrained requirement values (#10568) If we want to build `/debug-config`, we'll need to know the requirements sources that supplied the values. This PR adds those sources such that we can render them in the UI. --- codex-rs/core/src/config/mod.rs | 6 +- .../src/config_loader/config_requirements.rs | 110 ++++++++++++++---- codex-rs/core/src/config_loader/mod.rs | 1 + 3 files changed, 93 insertions(+), 24 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 949c08724f..d4547db19f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1588,9 +1588,9 @@ impl Config { model_provider_id, model_provider, cwd: resolved_cwd, - approval_policy: constrained_approval_policy, - sandbox_policy: constrained_sandbox_policy, - enforce_residency, + approval_policy: constrained_approval_policy.value, + sandbox_policy: constrained_sandbox_policy.value, + enforce_residency: enforce_residency.value, did_user_set_custom_approval_policy_or_sandbox_mode, forced_auto_mode_downgraded_on_windows, shell_environment_policy, diff --git a/codex-rs/core/src/config_loader/config_requirements.rs b/codex-rs/core/src/config_loader/config_requirements.rs index 478c74193e..660628c04f 100644 --- a/codex-rs/core/src/config_loader/config_requirements.rs +++ b/codex-rs/core/src/config_loader/config_requirements.rs @@ -44,25 +44,57 @@ impl fmt::Display for RequirementSource { } } +#[derive(Debug, Clone, PartialEq)] +pub struct ConstrainedWithSource { + pub value: Constrained, + pub source: Option, +} + +impl ConstrainedWithSource { + pub fn new(value: Constrained, source: Option) -> Self { + Self { value, source } + } +} + +impl std::ops::Deref for ConstrainedWithSource { + type Target = Constrained; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl std::ops::DerefMut for ConstrainedWithSource { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} + /// Normalized version of [`ConfigRequirementsToml`] after deserialization and /// normalization. #[derive(Debug, Clone, PartialEq)] pub struct ConfigRequirements { - pub approval_policy: Constrained, - pub sandbox_policy: Constrained, + pub approval_policy: ConstrainedWithSource, + pub sandbox_policy: ConstrainedWithSource, pub mcp_servers: Option>>, pub(crate) exec_policy: Option>, - pub enforce_residency: Constrained>, + pub enforce_residency: ConstrainedWithSource>, } impl Default for ConfigRequirements { fn default() -> Self { Self { - approval_policy: Constrained::allow_any_from_default(), - sandbox_policy: Constrained::allow_any(SandboxPolicy::ReadOnly), + approval_policy: ConstrainedWithSource::new( + Constrained::allow_any_from_default(), + None, + ), + sandbox_policy: ConstrainedWithSource::new( + Constrained::allow_any(SandboxPolicy::ReadOnly), + None, + ), mcp_servers: None, exec_policy: None, - enforce_residency: Constrained::allow_any(None), + enforce_residency: ConstrainedWithSource::new(Constrained::allow_any(None), None), } } } @@ -228,7 +260,7 @@ impl TryFrom for ConfigRequirements { enforce_residency, } = toml; - let approval_policy: Constrained = match allowed_approval_policies { + let approval_policy = match allowed_approval_policies { Some(Sourced { value: policies, source: requirement_source, @@ -237,7 +269,8 @@ impl TryFrom for ConfigRequirements { return Err(ConstraintError::empty_field("allowed_approval_policies")); }; - Constrained::new(initial_value, move |candidate| { + let requirement_source_for_error = requirement_source.clone(); + let constrained = Constrained::new(initial_value, move |candidate| { if policies.contains(candidate) { Ok(()) } else { @@ -245,12 +278,13 @@ impl TryFrom for ConfigRequirements { field_name: "approval_policy", candidate: format!("{candidate:?}"), allowed: format!("{policies:?}"), - requirement_source: requirement_source.clone(), + requirement_source: requirement_source_for_error.clone(), }) } - })? + })?; + ConstrainedWithSource::new(constrained, Some(requirement_source)) } - None => Constrained::allow_any_from_default(), + None => ConstrainedWithSource::new(Constrained::allow_any_from_default(), None), }; // TODO(gt): `ConfigRequirementsToml` should let the author specify the @@ -261,7 +295,7 @@ impl TryFrom for ConfigRequirements { // additional parameters. Ultimately, we should expand the config // format to allow specifying those parameters. let default_sandbox_policy = SandboxPolicy::ReadOnly; - let sandbox_policy: Constrained = match allowed_sandbox_modes { + let sandbox_policy = match allowed_sandbox_modes { Some(Sourced { value: modes, source: requirement_source, @@ -275,7 +309,8 @@ impl TryFrom for ConfigRequirements { }); }; - Constrained::new(default_sandbox_policy, move |candidate| { + let requirement_source_for_error = requirement_source.clone(); + let constrained = Constrained::new(default_sandbox_policy, move |candidate| { let mode = match candidate { SandboxPolicy::ReadOnly => SandboxModeRequirement::ReadOnly, SandboxPolicy::WorkspaceWrite { .. } => { @@ -293,12 +328,15 @@ impl TryFrom for ConfigRequirements { field_name: "sandbox_mode", candidate: format!("{mode:?}"), allowed: format!("{modes:?}"), - requirement_source: requirement_source.clone(), + requirement_source: requirement_source_for_error.clone(), }) } - })? + })?; + ConstrainedWithSource::new(constrained, Some(requirement_source)) + } + None => { + ConstrainedWithSource::new(Constrained::allow_any(default_sandbox_policy), None) } - None => Constrained::allow_any(default_sandbox_policy), }; let exec_policy = match rules { Some(Sourced { value, source }) => { @@ -313,13 +351,14 @@ impl TryFrom for ConfigRequirements { None => None, }; - let enforce_residency: Constrained> = match enforce_residency { + let enforce_residency = match enforce_residency { Some(Sourced { value: residency, source: requirement_source, }) => { let required = Some(residency); - Constrained::new(required, move |candidate| { + let requirement_source_for_error = requirement_source.clone(); + let constrained = Constrained::new(required, move |candidate| { if candidate == &required { Ok(()) } else { @@ -327,12 +366,13 @@ impl TryFrom for ConfigRequirements { field_name: "enforce_residency", candidate: format!("{candidate:?}"), allowed: format!("{required:?}"), - requirement_source: requirement_source.clone(), + requirement_source: requirement_source_for_error.clone(), }) } - })? + })?; + ConstrainedWithSource::new(constrained, Some(requirement_source)) } - None => Constrained::allow_any(None), + None => ConstrainedWithSource::new(Constrained::allow_any(None), None), }; Ok(ConfigRequirements { approval_policy, @@ -563,6 +603,34 @@ mod tests { Ok(()) } + #[test] + fn constrained_fields_store_requirement_source() -> Result<()> { + let source: ConfigRequirementsToml = from_str( + r#" + allowed_approval_policies = ["on-request"] + allowed_sandbox_modes = ["read-only"] + enforce_residency = "us" + "#, + )?; + + let source_location = RequirementSource::CloudRequirements; + let mut target = ConfigRequirementsWithSources::default(); + target.merge_unset_fields(source_location.clone(), source); + let requirements = ConfigRequirements::try_from(target)?; + + assert_eq!( + requirements.approval_policy.source, + Some(source_location.clone()) + ); + assert_eq!( + requirements.sandbox_policy.source, + Some(source_location.clone()) + ); + assert_eq!(requirements.enforce_residency.source, Some(source_location)); + + Ok(()) + } + #[test] fn deserialize_allowed_approval_policies() -> Result<()> { let toml_str = r#" diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index d8813e23d7..e9cd1c68e8 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -34,6 +34,7 @@ use toml::Value as TomlValue; pub use cloud_requirements::CloudRequirementsLoader; pub use config_requirements::ConfigRequirements; pub use config_requirements::ConfigRequirementsToml; +pub use config_requirements::ConstrainedWithSource; pub use config_requirements::McpServerIdentity; pub use config_requirements::McpServerRequirement; pub use config_requirements::RequirementSource; From 38f6c6b1141699e87fffb398372abe157e6c52c1 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 11:14:53 +0000 Subject: [PATCH 14/90] chore: simplify user message detection (#10611) We don't check anymore the response item with `user` role as they may be instructions etc --- codex-rs/core/src/rollout/list.rs | 2 +- codex-rs/core/src/rollout/recorder.rs | 2 + codex-rs/state/src/extract.rs | 119 +------------------------- 3 files changed, 6 insertions(+), 117 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 5e7c1aa350..34e144e568 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1125,7 +1125,7 @@ async fn find_thread_path_by_id_str_in_subdir( let found = results.matches.into_iter().next().map(|m| m.full_path()); if found.is_some() { tracing::error!("state db missing rollout path for thread {id_str}"); - state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "path_mismatch"); + state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "falling_back"); } Ok(found) diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index ab017810f3..465bcbbcdf 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -187,6 +187,8 @@ impl RolloutRecorder { populate_thread_heads(page.items.as_mut_slice()).await; return Ok(page); } + tracing::error!("Falling back on rollout system"); + state_db::record_discrepancy("list_threads_with_db_fallback", "falling_back"); if archived { let root = codex_home.join(ARCHIVED_SESSIONS_SUBDIR); diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index bfcd75416f..2bf3949751 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -1,21 +1,13 @@ use crate::model::ThreadMetadata; -use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; -use codex_protocol::models::is_local_image_close_tag_text; -use codex_protocol::models::is_local_image_open_tag_text; -use codex_protocol::protocol::ENVIRONMENT_CONTEXT_OPEN_TAG; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::TurnContextItem; -use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; use codex_protocol::protocol::USER_MESSAGE_BEGIN; use serde::Serialize; use serde_json::Value; -const USER_INSTRUCTIONS_PREFIX: &str = "# AGENTS.md instructions for "; -const TURN_ABORTED_OPEN_TAG: &str = ""; - /// Apply a rollout item to the metadata structure. pub fn apply_rollout_item( metadata: &mut ThreadMetadata, @@ -78,73 +70,8 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } } -fn apply_response_item(metadata: &mut ThreadMetadata, item: &ResponseItem) { - if !is_user_response_item(item) { - return; - } - metadata.has_user_event = true; - if metadata.title.is_empty() - && let Some(text) = extract_user_message_text(item) - { - metadata.title = text; - } -} - -// TODO(jif) unify once the discussion is settled -fn is_user_response_item(item: &ResponseItem) -> bool { - let ResponseItem::Message { role, content, .. } = item else { - return false; - }; - role == "user" - && !is_user_instructions(content.as_slice()) - && !is_session_prefix_content(content.as_slice()) -} - -fn is_user_instructions(content: &[ContentItem]) -> bool { - if let [ContentItem::InputText { text }] = content { - text.starts_with(USER_INSTRUCTIONS_PREFIX) || text.starts_with(USER_INSTRUCTIONS_OPEN_TAG) - } else { - false - } -} - -fn is_session_prefix_content(content: &[ContentItem]) -> bool { - if let [ContentItem::InputText { text }] = content { - is_session_prefix(text) - } else { - false - } -} - -fn is_session_prefix(text: &str) -> bool { - let lowered = text.trim_start().to_ascii_lowercase(); - lowered.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) || lowered.starts_with(TURN_ABORTED_OPEN_TAG) -} - -fn extract_user_message_text(item: &ResponseItem) -> Option { - let ResponseItem::Message { role, content, .. } = item else { - return None; - }; - if role != "user" { - return None; - } - let texts: Vec<&str> = content - .iter() - .filter_map(|content_item| match content_item { - ContentItem::InputText { text } => Some(text.as_str()), - ContentItem::InputImage { .. } | ContentItem::OutputText { .. } => None, - }) - .filter(|text| !is_local_image_open_tag_text(text) && !is_local_image_close_tag_text(text)) - .collect(); - if texts.is_empty() { - return None; - } - let joined = texts.join("\n"); - Some( - strip_user_message_prefix(joined.as_str()) - .trim() - .to_string(), - ) +fn apply_response_item(_metadata: &mut ThreadMetadata, _item: &ResponseItem) { + // TODO (jif) } fn strip_user_message_prefix(text: &str) -> &str { @@ -165,7 +92,6 @@ pub(crate) fn enum_to_string(value: &T) -> String { #[cfg(test)] mod tests { use super::apply_rollout_item; - use super::extract_user_message_text; use crate::model::ThreadMetadata; use chrono::DateTime; use chrono::Utc; @@ -174,31 +100,11 @@ mod tests { use codex_protocol::models::ResponseItem; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; - use codex_protocol::protocol::USER_MESSAGE_BEGIN; + use pretty_assertions::assert_eq; use std::path::PathBuf; use uuid::Uuid; - #[test] - fn extracts_user_message_text() { - let item = ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ - ContentItem::InputText { - text: format!(" {USER_MESSAGE_BEGIN}actual question"), - }, - ContentItem::InputImage { - image_url: "https://example.com/image.png".to_string(), - }, - ], - end_turn: None, - phase: None, - }; - let actual = extract_user_message_text(&item); - assert_eq!(actual.as_deref(), Some("actual question")); - } - #[test] fn user_instructions_do_not_count_as_user_events() { let mut metadata = metadata_for_test(); @@ -240,25 +146,6 @@ mod tests { assert_eq!(metadata.title, ""); } - #[test] - fn image_only_user_messages_still_count_as_user_events() { - let mut metadata = metadata_for_test(); - let item = RolloutItem::ResponseItem(ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputImage { - image_url: "https://example.com/image.png".to_string(), - }], - end_turn: None, - phase: None, - }); - - apply_rollout_item(&mut metadata, &item, "test-provider"); - - assert_eq!(metadata.has_user_event, true); - assert_eq!(metadata.title, ""); - } - fn metadata_for_test() -> ThreadMetadata { let id = ThreadId::from_string(&Uuid::from_u128(42).to_string()).expect("thread id"); let created_at = DateTime::::from_timestamp(1_735_689_600, 0).expect("timestamp"); From 61aecdde663047aee4b9f63a4ed34bc466bc6e4b Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 13:01:17 +0000 Subject: [PATCH 15/90] fix: make sure file exist in `find_thread_path_by_id_str_in_subdir` (#10618) --- codex-rs/core/src/rollout/list.rs | 15 ++++++++----- codex-rs/core/src/rollout/tests.rs | 21 +++++++++++++++++++ .../core/tests/suite/rollout_list_find.rs | 8 ++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 34e144e568..2cac898d42 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1091,17 +1091,22 @@ async fn find_thread_path_by_id_str_in_subdir( let state_db_ctx = state_db::open_if_present(codex_home, "").await; if let Some(state_db_ctx) = state_db_ctx.as_deref() && let Ok(thread_id) = ThreadId::from_string(id_str) - { - let db_path = state_db::find_rollout_path_by_id( + && let Some(db_path) = state_db::find_rollout_path_by_id( Some(state_db_ctx), thread_id, archived_only, "find_path_query", ) - .await; - if db_path.is_some() { - return Ok(db_path); + .await + { + if tokio::fs::try_exists(&db_path).await.unwrap_or(false) { + return Ok(Some(db_path)); } + tracing::error!( + "state db returned stale rollout path for thread {id_str}: {}", + db_path.display() + ); + state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "stale_db_path"); } let mut root = codex_home.to_path_buf(); diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index e42e186157..89df2897db 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -232,6 +232,27 @@ async fn list_threads_falls_back_to_files_when_state_db_is_unavailable() { ); } +#[tokio::test] +async fn find_thread_path_falls_back_when_db_path_is_stale() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let uuid = Uuid::from_u128(302); + let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id"); + let ts = "2025-01-03T13-00-00"; + write_session_file(home, ts, uuid, 1, Some(SessionSource::Cli)).unwrap(); + let fs_rollout_path = home.join(format!("sessions/2025/01/03/rollout-{ts}-{uuid}.jsonl")); + + let stale_db_path = home.join(format!( + "sessions/2099/01/01/rollout-2099-01-01T00-00-00-{uuid}.jsonl" + )); + insert_state_db_thread(home, thread_id, stale_db_path.as_path(), false).await; + + let found = crate::rollout::find_thread_path_by_id_str(home, &uuid.to_string()) + .await + .expect("lookup should succeed"); + assert_eq!(found, Some(fs_rollout_path)); +} + #[test] fn rollout_date_parts_extracts_directory_components() { let file_name = OsStr::new("rollout-2025-03-01T09-00-00-123.jsonl"); diff --git a/codex-rs/core/tests/suite/rollout_list_find.rs b/codex-rs/core/tests/suite/rollout_list_find.rs index 8786c9be21..5b545384b9 100644 --- a/codex-rs/core/tests/suite/rollout_list_find.rs +++ b/codex-rs/core/tests/suite/rollout_list_find.rs @@ -104,9 +104,11 @@ async fn find_prefers_sqlite_path_by_id() { let home = TempDir::new().unwrap(); let id = Uuid::new_v4(); let thread_id = ThreadId::from_string(&id.to_string()).unwrap(); - let db_path = home - .path() - .join("sessions/2030/12/30/rollout-2030-12-30T00-00-00-db.jsonl"); + let db_path = home.path().join(format!( + "sessions/2030/12/30/rollout-2030-12-30T00-00-00-{id}.jsonl" + )); + std::fs::create_dir_all(db_path.parent().unwrap()).unwrap(); + std::fs::write(&db_path, "").unwrap(); write_minimal_rollout_with_id(home.path(), id); upsert_thread_metadata(home.path(), thread_id, db_path.clone()).await; From aab60a55f1ca2aac2472fbc407b4dbc82c61d522 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 13:01:24 +0000 Subject: [PATCH 16/90] nit: cleaning (#10619) --- codex-rs/state/src/extract.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 2bf3949751..753d8e426d 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -71,7 +71,7 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } fn apply_response_item(_metadata: &mut ThreadMetadata, _item: &ResponseItem) { - // TODO (jif) + // Title and has_user_event are derived from EventMsg::UserMessage only. } fn strip_user_message_prefix(text: &str) -> &str { @@ -98,23 +98,23 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; + use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; - use codex_protocol::protocol::USER_INSTRUCTIONS_OPEN_TAG; + use codex_protocol::protocol::USER_MESSAGE_BEGIN; + use codex_protocol::protocol::UserMessageEvent; use pretty_assertions::assert_eq; use std::path::PathBuf; use uuid::Uuid; #[test] - fn user_instructions_do_not_count_as_user_events() { + fn response_item_user_messages_do_not_set_title_or_has_user_event() { let mut metadata = metadata_for_test(); let item = RolloutItem::ResponseItem(ResponseItem::Message { id: None, role: "user".to_string(), content: vec![ContentItem::InputText { - text: format!( - "# AGENTS.md instructions for /tmp\n\n{USER_INSTRUCTIONS_OPEN_TAG}test" - ), + text: "hello from response item".to_string(), }], end_turn: None, phase: None, @@ -127,23 +127,19 @@ mod tests { } #[test] - fn session_prefix_messages_do_not_count_as_user_events() { + fn event_msg_user_messages_set_title_and_has_user_event() { let mut metadata = metadata_for_test(); - let item = RolloutItem::ResponseItem(ResponseItem::Message { - id: None, - role: "user".to_string(), - content: vec![ContentItem::InputText { - text: "\n {\"cwd\":\"/tmp\"}" - .to_string(), - }], - end_turn: None, - phase: None, - }); + let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: format!("{USER_MESSAGE_BEGIN} actual user request"), + images: Some(vec![]), + local_images: vec![], + text_elements: vec![], + })); apply_rollout_item(&mut metadata, &item, "test-provider"); - assert_eq!(metadata.has_user_event, false); - assert_eq!(metadata.title, ""); + assert_eq!(metadata.has_user_event, true); + assert_eq!(metadata.title, "actual user request"); } fn metadata_for_test() -> ThreadMetadata { From df000da917952024edd14cb624af8d1e1740a2b5 Mon Sep 17 00:00:00 2001 From: Rasmus Rygaard Date: Wed, 4 Feb 2026 06:01:47 -0800 Subject: [PATCH 17/90] Add a codex.rate_limits event for websockets (#10324) When communicating over websockets, we can't rely on headers to deliver rate limit information. This PR adds a `codex.rate_limits` event that the server can pass to the client to inform them about rate limit usage. The client parses this data the same way we parse rate limit headers in HTTP mode. This PR also wires up the etag and reasoning headers for websockets --- .../src/endpoint/responses_websocket.rs | 29 ++++++- codex-rs/codex-api/src/rate_limits.rs | 66 ++++++++++++++ codex-rs/codex-api/src/sse/responses.rs | 6 ++ .../core/tests/suite/client_websockets.rs | 86 +++++++++++++++++++ 4 files changed, 183 insertions(+), 4 deletions(-) diff --git a/codex-rs/codex-api/src/endpoint/responses_websocket.rs b/codex-rs/codex-api/src/endpoint/responses_websocket.rs index cac686dd02..20c6067b17 100644 --- a/codex-rs/codex-api/src/endpoint/responses_websocket.rs +++ b/codex-rs/codex-api/src/endpoint/responses_websocket.rs @@ -5,6 +5,7 @@ use crate::common::ResponseStream; use crate::common::ResponsesWsRequest; use crate::error::ApiError; use crate::provider::Provider; +use crate::rate_limits::parse_rate_limit_event; use crate::sse::responses::ResponsesStreamEvent; use crate::sse::responses::process_responses_event; use crate::telemetry::WebsocketTelemetry; @@ -33,6 +34,7 @@ use url::Url; type WsStream = WebSocketStream>; const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; +const X_MODELS_ETAG_HEADER: &str = "x-models-etag"; const X_REASONING_INCLUDED_HEADER: &str = "x-reasoning-included"; pub struct ResponsesWebsocketConnection { @@ -40,6 +42,7 @@ pub struct ResponsesWebsocketConnection { // TODO (pakrym): is this the right place for timeout? idle_timeout: Duration, server_reasoning_included: bool, + models_etag: Option, telemetry: Option>, } @@ -48,12 +51,14 @@ impl ResponsesWebsocketConnection { stream: WsStream, idle_timeout: Duration, server_reasoning_included: bool, + models_etag: Option, telemetry: Option>, ) -> Self { Self { stream: Arc::new(Mutex::new(Some(stream))), idle_timeout, server_reasoning_included, + models_etag, telemetry, } } @@ -71,12 +76,16 @@ impl ResponsesWebsocketConnection { let stream = Arc::clone(&self.stream); let idle_timeout = self.idle_timeout; let server_reasoning_included = self.server_reasoning_included; + let models_etag = self.models_etag.clone(); let telemetry = self.telemetry.clone(); let request_body = serde_json::to_value(&request).map_err(|err| { ApiError::Stream(format!("failed to encode websocket request: {err}")) })?; tokio::spawn(async move { + if let Some(etag) = models_etag { + let _ = tx_event.send(Ok(ResponseEvent::ModelsEtag(etag))).await; + } if server_reasoning_included { let _ = tx_event .send(Ok(ResponseEvent::ServerReasoningIncluded(true))) @@ -136,12 +145,13 @@ impl ResponsesWebsocketClient { headers.extend(extra_headers); add_auth_headers_to_header_map(&self.auth, &mut headers); - let (stream, server_reasoning_included) = - connect_websocket(ws_url, headers, turn_state).await?; + let (stream, server_reasoning_included, models_etag) = + connect_websocket(ws_url, headers, turn_state.clone()).await?; Ok(ResponsesWebsocketConnection::new( stream, self.provider.stream_idle_timeout, server_reasoning_included, + models_etag, telemetry, )) } @@ -151,7 +161,7 @@ async fn connect_websocket( url: Url, headers: HeaderMap, turn_state: Option>>, -) -> Result<(WsStream, bool), ApiError> { +) -> Result<(WsStream, bool, Option), ApiError> { info!("connecting to websocket: {url}"); let mut request = url @@ -177,6 +187,11 @@ async fn connect_websocket( }; let reasoning_included = response.headers().contains_key(X_REASONING_INCLUDED_HEADER); + let models_etag = response + .headers() + .get(X_MODELS_ETAG_HEADER) + .and_then(|value| value.to_str().ok()) + .map(ToString::to_string); if let Some(turn_state) = turn_state && let Some(header_value) = response .headers() @@ -185,7 +200,7 @@ async fn connect_websocket( { let _ = turn_state.set(header_value.to_string()); } - Ok((stream, reasoning_included)) + Ok((stream, reasoning_included, models_etag)) } fn map_ws_error(err: WsError, url: &Url) -> ApiError { @@ -273,6 +288,12 @@ async fn run_websocket_response_stream( continue; } }; + if event.kind() == "codex.rate_limits" { + if let Some(snapshot) = parse_rate_limit_event(&text) { + let _ = tx_event.send(Ok(ResponseEvent::RateLimits(snapshot))).await; + } + continue; + } match process_responses_event(event) { Ok(Some(event)) => { let is_completed = matches!(event, ResponseEvent::Completed { .. }); diff --git a/codex-rs/codex-api/src/rate_limits.rs b/codex-rs/codex-api/src/rate_limits.rs index c29aab21f8..047c5934bd 100644 --- a/codex-rs/codex-api/src/rate_limits.rs +++ b/codex-rs/codex-api/src/rate_limits.rs @@ -1,7 +1,9 @@ +use codex_protocol::account::PlanType; use codex_protocol::protocol::CreditsSnapshot; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::RateLimitWindow; use http::HeaderMap; +use serde::Deserialize; use std::fmt::Display; #[derive(Debug)] @@ -41,6 +43,70 @@ pub fn parse_rate_limit(headers: &HeaderMap) -> Option { }) } +#[derive(Debug, Deserialize)] +struct RateLimitEventWindow { + used_percent: f64, + window_minutes: Option, + reset_at: Option, +} + +#[derive(Debug, Deserialize)] +struct RateLimitEventDetails { + primary: Option, + secondary: Option, +} + +#[derive(Debug, Deserialize)] +struct RateLimitEventCredits { + has_credits: bool, + unlimited: bool, + balance: Option, +} + +#[derive(Debug, Deserialize)] +struct RateLimitEvent { + #[serde(rename = "type")] + kind: String, + plan_type: Option, + rate_limits: Option, + credits: Option, +} + +pub fn parse_rate_limit_event(payload: &str) -> Option { + let event: RateLimitEvent = serde_json::from_str(payload).ok()?; + if event.kind != "codex.rate_limits" { + return None; + } + let (primary, secondary) = if let Some(details) = event.rate_limits.as_ref() { + ( + map_event_window(details.primary.as_ref()), + map_event_window(details.secondary.as_ref()), + ) + } else { + (None, None) + }; + let credits = event.credits.map(|credits| CreditsSnapshot { + has_credits: credits.has_credits, + unlimited: credits.unlimited, + balance: credits.balance, + }); + Some(RateLimitSnapshot { + primary, + secondary, + credits, + plan_type: event.plan_type, + }) +} + +fn map_event_window(window: Option<&RateLimitEventWindow>) -> Option { + let window = window?; + Some(RateLimitWindow { + used_percent: window.used_percent, + window_minutes: window.window_minutes, + resets_at: window.reset_at, + }) +} + /// Parses the bespoke Codex rate-limit headers into a `RateLimitSnapshot`. pub fn parse_promo_message(headers: &HeaderMap) -> Option { parse_header_str(headers, "x-codex-promo-message") diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index 2c91104727..95bda35fb2 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -165,6 +165,12 @@ pub struct ResponsesStreamEvent { content_index: Option, } +impl ResponsesStreamEvent { + pub fn kind(&self) -> &str { + &self.kind + } +} + #[derive(Debug)] pub enum ResponsesEventError { Api(ApiError), diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 9f66283442..15005caa48 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -18,6 +18,7 @@ use codex_otel::OtelManager; use codex_otel::metrics::MetricsClient; use codex_otel::metrics::MetricsConfig; use codex_protocol::ThreadId; +use codex_protocol::account::PlanType; use codex_protocol::config_types::ReasoningSummary; use core_test_support::load_default_config_for_test; use core_test_support::responses::WebSocketConnectionConfig; @@ -30,6 +31,7 @@ use core_test_support::skip_if_no_network; use futures::StreamExt; use opentelemetry_sdk::metrics::InMemoryMetricExporter; use pretty_assertions::assert_eq; +use serde_json::json; use std::sync::Arc; use std::time::Duration; use tempfile::TempDir; @@ -203,6 +205,90 @@ async fn responses_websocket_emits_reasoning_included_event() { server.shutdown().await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn responses_websocket_emits_rate_limit_events() { + skip_if_no_network!(); + + let rate_limit_event = json!({ + "type": "codex.rate_limits", + "plan_type": "plus", + "rate_limits": { + "allowed": true, + "limit_reached": false, + "primary": { + "used_percent": 42, + "window_minutes": 60, + "reset_at": 1700000000 + }, + "secondary": null + }, + "code_review_rate_limits": null, + "credits": { + "has_credits": true, + "unlimited": false, + "balance": "123" + }, + "promo": null + }); + + let server = start_websocket_server_with_headers(vec![WebSocketConnectionConfig { + requests: vec![vec![ + rate_limit_event, + ev_response_created("resp-1"), + ev_completed("resp-1"), + ]], + response_headers: vec![ + ("X-Models-Etag".to_string(), "etag-123".to_string()), + ("X-Reasoning-Included".to_string(), "true".to_string()), + ], + }]) + .await; + + let harness = websocket_harness(&server).await; + let mut session = harness.client.new_session(None); + let prompt = prompt_with_input(vec![message_item("hello")]); + + let mut stream = session + .stream(&prompt) + .await + .expect("websocket stream failed"); + + let mut saw_rate_limits = None; + let mut saw_models_etag = None; + let mut saw_reasoning_included = false; + + while let Some(event) = stream.next().await { + match event.expect("event") { + ResponseEvent::RateLimits(snapshot) => { + saw_rate_limits = Some(snapshot); + } + ResponseEvent::ModelsEtag(etag) => { + saw_models_etag = Some(etag); + } + ResponseEvent::ServerReasoningIncluded(true) => { + saw_reasoning_included = true; + } + ResponseEvent::Completed { .. } => break, + _ => {} + } + } + + let rate_limits = saw_rate_limits.expect("missing rate limits"); + let primary = rate_limits.primary.expect("missing primary window"); + assert_eq!(primary.used_percent, 42.0); + assert_eq!(primary.window_minutes, Some(60)); + assert_eq!(primary.resets_at, Some(1_700_000_000)); + assert_eq!(rate_limits.plan_type, Some(PlanType::Plus)); + let credits = rate_limits.credits.expect("missing credits"); + assert!(credits.has_credits); + assert!(!credits.unlimited); + assert_eq!(credits.balance.as_deref(), Some("123")); + assert_eq!(saw_models_etag.as_deref(), Some("etag-123")); + assert!(saw_reasoning_included); + + server.shutdown().await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn responses_websocket_appends_on_prefix() { skip_if_no_network!(); From 583e5d4f413dc10457e685d26672529ba7d2f70e Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 14:31:12 +0000 Subject: [PATCH 18/90] Migrate state DB path helpers to versioned filename (#10623) Summary - add versioned state sqlite filename helpers and re-export them from the state crate - remove legacy state files when initializing the runtime and update consumers/tests to use the new helpers - tweak logs client description and database resolution to match the new path --- codex-rs/core/src/state_db.rs | 7 +- codex-rs/core/tests/suite/sqlite_state.rs | 7 +- codex-rs/state/src/bin/logs_client.rs | 5 +- codex-rs/state/src/lib.rs | 3 + codex-rs/state/src/runtime.rs | 171 +++++++++++++++++++++- 5 files changed, 180 insertions(+), 13 deletions(-) diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index 7a0894a327..1cfb8f1566 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -14,7 +14,6 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionSource; use codex_state::DB_METRIC_COMPARE_ERROR; pub use codex_state::LogEntry; -use codex_state::STATE_DB_FILENAME; use codex_state::ThreadMetadataBuilder; use serde_json::Value; use std::path::Path; @@ -32,7 +31,7 @@ pub(crate) async fn init_if_enabled( config: &Config, otel: Option<&OtelManager>, ) -> Option { - let state_path = config.codex_home.join(STATE_DB_FILENAME); + let state_path = codex_state::state_db_path(config.codex_home.as_path()); if !config.features.enabled(Feature::Sqlite) { return None; } @@ -74,7 +73,7 @@ pub(crate) async fn init_if_enabled( /// Get the DB if the feature is enabled and the DB exists. pub async fn get_state_db(config: &Config, otel: Option<&OtelManager>) -> Option { - let state_path = config.codex_home.join(STATE_DB_FILENAME); + let state_path = codex_state::state_db_path(config.codex_home.as_path()); if !config.features.enabled(Feature::Sqlite) || !tokio::fs::try_exists(&state_path).await.unwrap_or(false) { @@ -93,7 +92,7 @@ pub async fn get_state_db(config: &Config, otel: Option<&OtelManager>) -> Option /// /// This is used for parity checks during the SQLite migration phase. pub async fn open_if_present(codex_home: &Path, default_provider: &str) -> Option { - let db_path = codex_home.join(STATE_DB_FILENAME); + let db_path = codex_state::state_db_path(codex_home); if !tokio::fs::try_exists(&db_path).await.unwrap_or(false) { return None; } diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 218da34825..ac71c14d36 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -9,7 +9,6 @@ use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::UserMessageEvent; -use codex_state::STATE_DB_FILENAME; use core_test_support::load_sse_fixture_with_id; use core_test_support::responses; use core_test_support::responses::ev_completed; @@ -39,7 +38,7 @@ async fn new_thread_is_recorded_in_state_db() -> Result<()> { let thread_id = test.session_configured.session_id; let rollout_path = test.codex.rollout_path().expect("rollout path"); - let db_path = test.config.codex_home.join(STATE_DB_FILENAME); + let db_path = codex_state::state_db_path(test.config.codex_home.as_path()); for _ in 0..100 { if tokio::fs::try_exists(&db_path).await.unwrap_or(false) { @@ -149,7 +148,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { let test = builder.build(&server).await?; - let db_path = test.config.codex_home.join(STATE_DB_FILENAME); + let db_path = codex_state::state_db_path(test.config.codex_home.as_path()); let rollout_path = test.config.codex_home.join(&rollout_rel_path); let default_provider = test.config.model_provider_id.clone(); @@ -205,7 +204,7 @@ async fn user_messages_persist_in_state_db() -> Result<()> { }); let test = builder.build(&server).await?; - let db_path = test.config.codex_home.join(STATE_DB_FILENAME); + let db_path = codex_state::state_db_path(test.config.codex_home.as_path()); for _ in 0..100 { if tokio::fs::try_exists(&db_path).await.unwrap_or(false) { break; diff --git a/codex-rs/state/src/bin/logs_client.rs b/codex-rs/state/src/bin/logs_client.rs index d1329a1a89..796a968cb8 100644 --- a/codex-rs/state/src/bin/logs_client.rs +++ b/codex-rs/state/src/bin/logs_client.rs @@ -6,14 +6,13 @@ use chrono::DateTime; use clap::Parser; use codex_state::LogQuery; use codex_state::LogRow; -use codex_state::STATE_DB_FILENAME; use codex_state::StateRuntime; use dirs::home_dir; use owo_colors::OwoColorize; #[derive(Debug, Parser)] #[command(name = "codex-state-logs")] -#[command(about = "Tail Codex logs from state.sqlite with simple filters")] +#[command(about = "Tail Codex logs from the state SQLite DB with simple filters")] struct Args { /// Path to CODEX_HOME. Defaults to $CODEX_HOME or ~/.codex. #[arg(long, env = "CODEX_HOME")] @@ -104,7 +103,7 @@ fn resolve_db_path(args: &Args) -> anyhow::Result { } let codex_home = args.codex_home.clone().unwrap_or_else(default_codex_home); - Ok(codex_home.join(STATE_DB_FILENAME)) + Ok(codex_state::state_db_path(codex_home.as_path())) } fn default_codex_home() -> PathBuf { diff --git a/codex-rs/state/src/lib.rs b/codex-rs/state/src/lib.rs index c08c76a1cf..2d37ecee96 100644 --- a/codex-rs/state/src/lib.rs +++ b/codex-rs/state/src/lib.rs @@ -29,6 +29,9 @@ pub use model::ThreadMetadata; pub use model::ThreadMetadataBuilder; pub use model::ThreadsPage; pub use runtime::STATE_DB_FILENAME; +pub use runtime::STATE_DB_VERSION; +pub use runtime::state_db_filename; +pub use runtime::state_db_path; /// Errors encountered during DB operations. Tags: [stage] pub const DB_ERROR_METRIC: &str = "codex.db.error"; diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index c64b7d56c6..f4c2d76e8a 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -35,7 +35,8 @@ use std::sync::Arc; use std::time::Duration; use tracing::warn; -pub const STATE_DB_FILENAME: &str = "state.sqlite"; +pub const STATE_DB_FILENAME: &str = "state"; +pub const STATE_DB_VERSION: u32 = 2; const METRIC_DB_INIT: &str = "codex.db.init"; @@ -56,7 +57,8 @@ impl StateRuntime { otel: Option, ) -> anyhow::Result> { tokio::fs::create_dir_all(&codex_home).await?; - let state_path = codex_home.join(STATE_DB_FILENAME); + remove_legacy_state_files(&codex_home).await; + let state_path = state_db_path(codex_home.as_path()); let existed = tokio::fs::try_exists(&state_path).await.unwrap_or(false); let pool = match open_sqlite(&state_path).await { Ok(db) => Arc::new(db), @@ -624,6 +626,77 @@ async fn open_sqlite(path: &Path) -> anyhow::Result { Ok(pool) } +pub fn state_db_filename() -> String { + format!("{STATE_DB_FILENAME}_{STATE_DB_VERSION}.sqlite") +} + +pub fn state_db_path(codex_home: &Path) -> PathBuf { + codex_home.join(state_db_filename()) +} + +async fn remove_legacy_state_files(codex_home: &Path) { + let current_name = state_db_filename(); + let mut entries = match tokio::fs::read_dir(codex_home).await { + Ok(entries) => entries, + Err(err) => { + warn!( + "failed to read codex_home for state db cleanup {}: {err}", + codex_home.display() + ); + return; + } + }; + while let Ok(Some(entry)) = entries.next_entry().await { + if !entry + .file_type() + .await + .map(|file_type| file_type.is_file()) + .unwrap_or(false) + { + continue; + } + let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); + if !should_remove_state_file(file_name.as_ref(), current_name.as_str()) { + continue; + } + + let legacy_path = entry.path(); + if let Err(err) = tokio::fs::remove_file(&legacy_path).await { + warn!( + "failed to remove legacy state db file {}: {err}", + legacy_path.display() + ); + } + } +} + +fn should_remove_state_file(file_name: &str, current_name: &str) -> bool { + let mut base_name = file_name; + for suffix in ["-wal", "-shm", "-journal"] { + if let Some(stripped) = file_name.strip_suffix(suffix) { + base_name = stripped; + break; + } + } + if base_name == current_name { + return false; + } + let unversioned_name = format!("{STATE_DB_FILENAME}.sqlite"); + if base_name == unversioned_name { + return true; + } + + let Some(version_with_extension) = base_name.strip_prefix(&format!("{STATE_DB_FILENAME}_")) + else { + return false; + }; + let Some(version_suffix) = version_with_extension.strip_suffix(".sqlite") else { + return false; + }; + !version_suffix.is_empty() && version_suffix.chars().all(|ch| ch.is_ascii_digit()) +} + fn push_thread_filters<'a>( builder: &mut QueryBuilder<'a, Sqlite>, archived_only: bool, @@ -692,3 +765,97 @@ fn push_thread_order_and_limit( builder.push(" LIMIT "); builder.push_bind(limit as i64); } + +#[cfg(test)] +mod tests { + use super::STATE_DB_FILENAME; + use super::STATE_DB_VERSION; + use super::StateRuntime; + use super::state_db_filename; + use pretty_assertions::assert_eq; + use std::path::PathBuf; + use std::time::SystemTime; + use std::time::UNIX_EPOCH; + + fn unique_temp_dir() -> PathBuf { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_or(0, |duration| duration.as_nanos()); + std::env::temp_dir().join(format!("codex-state-runtime-test-{nanos}")) + } + + #[tokio::test] + async fn init_removes_legacy_state_db_files() { + let codex_home = unique_temp_dir(); + tokio::fs::create_dir_all(&codex_home) + .await + .expect("create codex_home"); + + let current_name = state_db_filename(); + let previous_version = STATE_DB_VERSION.saturating_sub(1); + let unversioned_name = format!("{STATE_DB_FILENAME}.sqlite"); + for suffix in ["", "-wal", "-shm", "-journal"] { + let path = codex_home.join(format!("{unversioned_name}{suffix}")); + tokio::fs::write(path, b"legacy") + .await + .expect("write legacy"); + let old_version_path = codex_home.join(format!( + "{STATE_DB_FILENAME}_{previous_version}.sqlite{suffix}" + )); + tokio::fs::write(old_version_path, b"old_version") + .await + .expect("write old version"); + } + let unrelated_path = codex_home.join("state.sqlite_backup"); + tokio::fs::write(&unrelated_path, b"keep") + .await + .expect("write unrelated"); + let numeric_path = codex_home.join("123"); + tokio::fs::write(&numeric_path, b"keep") + .await + .expect("write numeric"); + + let _runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + for suffix in ["", "-wal", "-shm", "-journal"] { + let legacy_path = codex_home.join(format!("{unversioned_name}{suffix}")); + assert_eq!( + tokio::fs::try_exists(&legacy_path) + .await + .expect("check legacy path"), + false + ); + let old_version_path = codex_home.join(format!( + "{STATE_DB_FILENAME}_{previous_version}.sqlite{suffix}" + )); + assert_eq!( + tokio::fs::try_exists(&old_version_path) + .await + .expect("check old version path"), + false + ); + } + assert_eq!( + tokio::fs::try_exists(codex_home.join(current_name)) + .await + .expect("check new db path"), + true + ); + assert_eq!( + tokio::fs::try_exists(&unrelated_path) + .await + .expect("check unrelated path"), + true + ); + assert_eq!( + tokio::fs::try_exists(&numeric_path) + .await + .expect("check numeric path"), + true + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } +} From 0efd33f7f4291d6a056dc39a9654ce04ebe0400c Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Wed, 4 Feb 2026 08:38:06 -0800 Subject: [PATCH 19/90] Update tests to stop using sse_completed fixture (#10638) Summary: - replace the `sse_completed` fixture and related JSON template with direct `responses::ev_completed` payload builders - cascade the new SSE helpers through all affected core tests for consistency and clarity - remove legacy fixtures that were no longer needed once the helpers are in place Testing: - Not run (not requested) --- codex-rs/core/tests/common/lib.rs | 35 ----- .../tests/fixtures/completed_template.json | 16 --- codex-rs/core/tests/suite/client.rs | 124 ++++++++++++++---- .../tests/suite/collaboration_instructions.rs | 100 +++++++++++--- codex-rs/core/tests/suite/fork_thread.rs | 10 +- .../core/tests/suite/hierarchical_agents.rs | 20 ++- codex-rs/core/tests/suite/model_tools.rs | 10 +- .../core/tests/suite/permissions_messages.rs | 82 +++++++++--- codex-rs/core/tests/suite/personality.rs | 48 +++++-- codex-rs/core/tests/suite/prompt_caching.rs | 99 +++++++++++--- codex-rs/core/tests/suite/sqlite_state.rs | 10 +- .../suite/stream_error_allows_next_turn.rs | 16 ++- .../core/tests/suite/stream_no_completed.rs | 16 ++- codex-rs/core/tests/suite/web_search.rs | 36 +++-- 14 files changed, 428 insertions(+), 194 deletions(-) delete mode 100644 codex-rs/core/tests/fixtures/completed_template.json diff --git a/codex-rs/core/tests/common/lib.rs b/codex-rs/core/tests/common/lib.rs index 5df2a74edd..3feadb0722 100644 --- a/codex-rs/core/tests/common/lib.rs +++ b/codex-rs/core/tests/common/lib.rs @@ -1,7 +1,6 @@ #![expect(clippy::expect_used)] use codex_utils_cargo_bin::CargoBinError; -use codex_utils_cargo_bin::find_resource; use tempfile::TempDir; use codex_core::CodexThread; @@ -147,40 +146,6 @@ pub fn load_sse_fixture_with_id_from_str(raw: &str, id: &str) -> String { .collect() } -/// Same as [`load_sse_fixture`], but replaces the placeholder `__ID__` in the -/// fixture template with the supplied identifier before parsing. This lets a -/// single JSON template be reused by multiple tests that each need a unique -/// `response_id`. -pub fn load_sse_fixture_with_id(path: impl AsRef, id: &str) -> String { - let p = path.as_ref(); - let full_path = match find_resource!(p) { - Ok(p) => p, - Err(err) => panic!( - "failed to find fixture template at {:?}: {err}", - path.as_ref() - ), - }; - - let raw = std::fs::read_to_string(full_path).expect("read fixture template"); - let replaced = raw.replace("__ID__", id); - let events: Vec = - serde_json::from_str(&replaced).expect("parse JSON fixture"); - events - .into_iter() - .map(|e| { - let kind = e - .get("type") - .and_then(|v| v.as_str()) - .expect("fixture event missing type"); - if e.as_object().map(|o| o.len() == 1).unwrap_or(false) { - format!("event: {kind}\n\n") - } else { - format!("event: {kind}\ndata: {e}\n\n") - } - }) - .collect() -} - pub async fn wait_for_event(codex: &CodexThread, predicate: F) -> codex_core::protocol::EventMsg where F: FnMut(&codex_core::protocol::EventMsg) -> bool, diff --git a/codex-rs/core/tests/fixtures/completed_template.json b/codex-rs/core/tests/fixtures/completed_template.json deleted file mode 100644 index 1774dc5e84..0000000000 --- a/codex-rs/core/tests/fixtures/completed_template.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "type": "response.completed", - "response": { - "id": "__ID__", - "usage": { - "input_tokens": 0, - "input_tokens_details": null, - "output_tokens": 0, - "output_tokens_details": null, - "total_tokens": 0 - }, - "output": [] - } - } -] diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index bd50708a2c..62135127f2 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -36,8 +36,9 @@ use codex_protocol::models::WebSearchAction; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::user_input::UserInput; use core_test_support::load_default_config_for_test; -use core_test_support::load_sse_fixture_with_id; +use core_test_support::responses::ev_completed; use core_test_support::responses::ev_completed_with_tokens; +use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; use core_test_support::responses::mount_sse_once_match; use core_test_support::responses::mount_sse_sequence; @@ -64,11 +65,6 @@ use wiremock::matchers::method; use wiremock::matchers::path; use wiremock::matchers::query_param; -/// Build minimal SSE stream with completed marker using the JSON fixture. -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[expect(clippy::unwrap_used)] fn assert_message_role(request_body: &serde_json::Value, role: &str) { assert_eq!(request_body["role"].as_str().unwrap(), role); @@ -259,7 +255,11 @@ async fn resume_includes_initial_messages_and_sends_prior_items() { // Mock server that will receive the resumed request let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; // Configure Codex to resume from our file let codex_home = Arc::new(TempDir::new().unwrap()); @@ -377,7 +377,11 @@ async fn includes_conversation_id_and_model_headers_in_request() { // Mock server let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut builder = test_codex().with_auth(CodexAuth::from_api_key("Test API Key")); let test = builder @@ -418,7 +422,11 @@ async fn includes_base_instructions_override_in_request() { skip_if_no_network!(); // Mock server let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut builder = test_codex() .with_auth(CodexAuth::from_api_key("Test API Key")) @@ -462,7 +470,11 @@ async fn chatgpt_auth_sends_correct_request() { // Mock server let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut model_provider = built_in_model_providers()["openai"].clone(); model_provider.base_url = Some(format!("{}/api/codex", server.uri())); @@ -524,7 +536,10 @@ async fn prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens() { let first = ResponseTemplate::new(200) .insert_header("content-type", "text/event-stream") - .set_body_raw(sse_completed("resp1"), "text/event-stream"); + .set_body_raw( + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + "text/event-stream", + ); // Expect API key header, no ChatGPT account header required. Mock::given(method("POST")) @@ -590,7 +605,11 @@ async fn includes_user_instructions_message_in_request() { skip_if_no_network!(); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut builder = test_codex() .with_auth(CodexAuth::from_api_key("Test API Key")) @@ -652,7 +671,11 @@ async fn skills_append_to_instructions() { skip_if_no_network!(); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let codex_home = Arc::new(TempDir::new().unwrap()); let skill_dir = codex_home.path().join("skills/demo"); @@ -720,7 +743,11 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_model("gpt-5.1-codex") .with_config(|config| { @@ -761,7 +788,11 @@ async fn includes_no_effort_in_request() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_model("gpt-5.1-codex") .build(&server) @@ -800,7 +831,11 @@ async fn includes_default_reasoning_effort_in_request_when_defined_by_model_info skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex().with_model("gpt-5.1").build(&server).await?; codex @@ -835,7 +870,11 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, config, @@ -893,7 +932,11 @@ async fn configured_reasoning_summary_is_sent() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_config(|config| { config.model_reasoning_summary = ReasoningSummary::Concise; @@ -933,7 +976,11 @@ async fn reasoning_summary_is_omitted_when_disabled() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_config(|config| { config.model_reasoning_summary = ReasoningSummary::None; @@ -972,7 +1019,11 @@ async fn includes_default_verbosity_in_request() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex().with_model("gpt-5.1").build(&server).await?; codex @@ -1007,7 +1058,11 @@ async fn configured_verbosity_not_sent_for_models_without_support() -> anyhow::R skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_model("gpt-5.1-codex") .with_config(|config| { @@ -1047,7 +1102,11 @@ async fn configured_verbosity_is_sent() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_model("gpt-5.1") .with_config(|config| { @@ -1088,7 +1147,11 @@ async fn includes_developer_instructions_message_in_request() { skip_if_no_network!(); let server = MockServer::start().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut builder = test_codex() .with_auth(CodexAuth::from_api_key("Test API Key")) .with_config(|config| { @@ -1570,7 +1633,10 @@ async fn context_window_error_sets_total_tokens_to_model_window() -> anyhow::Res mount_sse_once_match( &server, body_string_contains("seed turn"), - sse_completed("resp_seed"), + sse(vec![ + ev_response_created("resp_seed"), + ev_completed("resp_seed"), + ]), ) .await; @@ -1656,7 +1722,10 @@ async fn azure_overrides_assign_properties_used_for_responses_url() { // First request – must NOT include `previous_response_id`. let first = ResponseTemplate::new(200) .insert_header("content-type", "text/event-stream") - .set_body_raw(sse_completed("resp1"), "text/event-stream"); + .set_body_raw( + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + "text/event-stream", + ); // Expect POST to /openai/responses with api-version query param Mock::given(method("POST")) @@ -1737,7 +1806,10 @@ async fn env_var_overrides_loaded_auth() { // First request – must NOT include `previous_response_id`. let first = ResponseTemplate::new(200) .insert_header("content-type", "text/event-stream") - .set_body_raw(sse_completed("resp1"), "text/event-stream"); + .set_body_raw( + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + "text/event-stream", + ); // Expect POST to /openai/responses with api-version query param Mock::given(method("POST")) diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index 21a0b27613..dc9905379e 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -18,10 +18,6 @@ use core_test_support::wait_for_event; use pretty_assertions::assert_eq; use serde_json::Value; -fn sse_completed(id: &str) -> String { - sse(vec![ev_response_created(id), ev_completed(id)]) -} - fn collab_mode_with_mode_and_instructions( mode: ModeKind, instructions: Option<&str>, @@ -72,7 +68,11 @@ async fn no_collaboration_instructions_by_default() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let test = test_codex().build(&server).await?; @@ -100,7 +100,11 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let test = test_codex().build(&server).await?; @@ -144,7 +148,11 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let test = test_codex().build(&server).await?; let collab_text = "turn instructions"; @@ -182,7 +190,11 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let test = test_codex().build(&server).await?; let collab_text = "override instructions"; @@ -226,7 +238,11 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let test = test_codex().build(&server).await?; let base_text = "base instructions"; @@ -282,8 +298,16 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()> skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let test = test_codex().build(&server).await?; let first_text = "first instructions"; @@ -354,8 +378,16 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let test = test_codex().build(&server).await?; let collab_text = "same instructions"; @@ -423,8 +455,16 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let test = test_codex().build(&server).await?; let default_text = "default mode instructions"; @@ -501,8 +541,16 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged() skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let test = test_codex().build(&server).await?; let collab_text = "mode-stable instructions"; @@ -576,8 +624,16 @@ async fn resume_replays_collaboration_instructions() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let mut builder = test_codex(); let initial = builder.build(&server).await?; @@ -642,7 +698,11 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let test = test_codex().build(&server).await?; diff --git a/codex-rs/core/tests/suite/fork_thread.rs b/codex-rs/core/tests/suite/fork_thread.rs index 55474a68aa..d5363cc0a9 100644 --- a/codex-rs/core/tests/suite/fork_thread.rs +++ b/codex-rs/core/tests/suite/fork_thread.rs @@ -6,6 +6,9 @@ use codex_core::protocol::RolloutItem; use codex_core::protocol::RolloutLine; use codex_protocol::items::TurnItem; use codex_protocol::user_input::UserInput; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; +use core_test_support::responses::sse; use core_test_support::skip_if_no_network; use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; @@ -15,18 +18,13 @@ use wiremock::ResponseTemplate; use wiremock::matchers::method; use wiremock::matchers::path; -/// Build minimal SSE stream with completed marker using the JSON fixture. -fn sse_completed(id: &str) -> String { - core_test_support::load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn fork_thread_twice_drops_to_first_message() { skip_if_no_network!(); // Start a mock server that completes three turns. let server = MockServer::start().await; - let sse = sse_completed("resp"); + let sse = sse(vec![ev_response_created("resp"), ev_completed("resp")]); let first = ResponseTemplate::new(200) .insert_header("content-type", "text/event-stream") .set_body_raw(sse.clone(), "text/event-stream"); diff --git a/codex-rs/core/tests/suite/hierarchical_agents.rs b/codex-rs/core/tests/suite/hierarchical_agents.rs index 5dfa636489..5f1f84340d 100644 --- a/codex-rs/core/tests/suite/hierarchical_agents.rs +++ b/codex-rs/core/tests/suite/hierarchical_agents.rs @@ -1,20 +1,22 @@ use codex_core::features::Feature; -use core_test_support::load_sse_fixture_with_id; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; +use core_test_support::responses::sse; use core_test_support::responses::start_mock_server; use core_test_support::test_codex::test_codex; const HIERARCHICAL_AGENTS_SNIPPET: &str = "Files called AGENTS.md commonly appear in many places inside a container"; -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn hierarchical_agents_appends_to_project_doc_in_user_instructions() { let server = start_mock_server().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut builder = test_codex().with_config(|config| { config.features.enable(Feature::ChildAgentsMd); @@ -49,7 +51,11 @@ async fn hierarchical_agents_appends_to_project_doc_in_user_instructions() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn hierarchical_agents_emits_when_no_project_doc() { let server = start_mock_server().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; let mut builder = test_codex().with_config(|config| { config.features.enable(Feature::ChildAgentsMd); diff --git a/codex-rs/core/tests/suite/model_tools.rs b/codex-rs/core/tests/suite/model_tools.rs index b08b5281c8..c687545f26 100644 --- a/codex-rs/core/tests/suite/model_tools.rs +++ b/codex-rs/core/tests/suite/model_tools.rs @@ -2,16 +2,11 @@ use codex_core::features::Feature; use codex_protocol::config_types::WebSearchMode; -use core_test_support::load_sse_fixture_with_id; use core_test_support::responses; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::test_codex; -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[allow(clippy::expect_used)] fn tool_identifiers(body: &serde_json::Value) -> Vec { body["tools"] @@ -31,7 +26,10 @@ fn tool_identifiers(body: &serde_json::Value) -> Vec { #[allow(clippy::expect_used)] async fn collect_tool_identifiers_for_model(model: &str) -> Vec { let server = start_mock_server().await; - let sse = sse_completed(model); + let sse = responses::sse(vec![ + responses::ev_response_created(model), + responses::ev_completed(model), + ]); let resp_mock = responses::mount_sse_once(&server, sse).await; let mut builder = test_codex() diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index c54203b4f1..fc1f0fa0b6 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -43,16 +43,16 @@ fn permissions_texts(input: &[serde_json::Value]) -> Vec { .collect() } -fn sse_completed(id: &str) -> String { - sse(vec![ev_response_created(id), ev_completed(id)]) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn permissions_message_sent_once_on_start() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let mut builder = test_codex().with_config(move |config| { config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -84,8 +84,16 @@ async fn permissions_message_added_on_override_change() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let mut builder = test_codex().with_config(move |config| { config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -148,8 +156,16 @@ async fn permissions_message_not_added_when_no_change() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let mut builder = test_codex().with_config(move |config| { config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -197,9 +213,21 @@ async fn resume_replays_permissions_messages() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let _req2 = mount_sse_once(&server, sse_completed("resp-2")).await; - let req3 = mount_sse_once(&server, sse_completed("resp-3")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let _req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; + let req3 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-3"), ev_completed("resp-3")]), + ) + .await; let mut builder = test_codex().with_config(|config| { config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -279,10 +307,26 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let _req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; - let req3 = mount_sse_once(&server, sse_completed("resp-3")).await; - let req4 = mount_sse_once(&server, sse_completed("resp-4")).await; + let _req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; + let req3 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-3"), ev_completed("resp-3")]), + ) + .await; + let req4 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-4"), ev_completed("resp-4")]), + ) + .await; let mut builder = test_codex().with_config(|config| { config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest); @@ -404,7 +448,11 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let writable = TempDir::new()?; let writable_root = AbsolutePathBuf::try_from(writable.path())?; let sandbox_policy = SandboxPolicy::WorkspaceWrite { diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 87978ceb5a..919ce852ed 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -42,10 +42,6 @@ const LOCAL_FRIENDLY_TEMPLATE: &str = "You optimize for team morale and being a supportive teammate as much as code quality."; const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer."; -fn sse_completed(id: &str) -> String { - sse(vec![ev_response_created(id), ev_completed(id)]) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn personality_does_not_mutate_base_instructions_without_template() { let codex_home = TempDir::new().expect("create temp dir"); @@ -82,7 +78,11 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { @@ -128,7 +128,11 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result< skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { @@ -184,7 +188,10 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> let server = start_mock_server().await; let resp_mock = mount_sse_sequence( &server, - vec![sse_completed("resp-1"), sse_completed("resp-2")], + vec![ + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ], ) .await; let mut builder = test_codex() @@ -280,7 +287,10 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho let server = start_mock_server().await; let resp_mock = mount_sse_sequence( &server, - vec![sse_completed("resp-1"), sse_completed("resp-2")], + vec![ + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ], ) .await; let mut builder = test_codex() @@ -387,7 +397,10 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> let server = start_mock_server().await; let resp_mock = mount_sse_sequence( &server, - vec![sse_completed("resp-1"), sse_completed("resp-2")], + vec![ + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ], ) .await; let mut builder = test_codex() @@ -524,7 +537,11 @@ async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Resul ) .await; - let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) @@ -640,7 +657,11 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow ) .await; - let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; + let resp_mock = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) @@ -753,7 +774,10 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - let resp_mock = mount_sse_sequence( &server, - vec![sse_completed("resp-1"), sse_completed("resp-2")], + vec![ + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ], ) .await; diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index bf1819cbf1..810cd52497 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -18,8 +18,10 @@ use codex_protocol::config_types::WebSearchMode; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::user_input::UserInput; use codex_utils_absolute_path::AbsolutePathBuf; -use core_test_support::load_sse_fixture_with_id; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; +use core_test_support::responses::sse; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; @@ -46,11 +48,6 @@ fn default_env_context_str(cwd: &str, shell: &Shell) -> String { ) } -/// Build minimal SSE stream with completed marker using the JSON fixture. -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - fn assert_tool_names(body: &serde_json::Value, expected_names: &[&str]) { assert_eq!( body["tools"] @@ -79,8 +76,16 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { use pretty_assertions::assert_eq; let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, @@ -172,8 +177,16 @@ async fn codex_mini_latest_tools() -> anyhow::Result<()> { use pretty_assertions::assert_eq; let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_config(|config| { @@ -238,8 +251,16 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests use pretty_assertions::assert_eq; let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, config, .. } = test_codex() .with_config(|config| { @@ -315,8 +336,16 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an use pretty_assertions::assert_eq; let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_config(|config| { @@ -407,7 +436,11 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let req = mount_sse_once(&server, sse_completed("resp-1")).await; + let req = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; let TestCodex { codex, .. } = test_codex().build(&server).await?; @@ -542,8 +575,16 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res use pretty_assertions::assert_eq; let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, .. } = test_codex() .with_config(|config| { @@ -646,8 +687,16 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a use pretty_assertions::assert_eq; let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, @@ -747,8 +796,16 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu let server = start_mock_server().await; - let req1 = mount_sse_once(&server, sse_completed("resp-1")).await; - let req2 = mount_sse_once(&server, sse_completed("resp-2")).await; + let req1 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let req2 = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ) + .await; let TestCodex { codex, config, diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index ac71c14d36..07268f4fdb 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -9,7 +9,6 @@ use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::UserMessageEvent; -use core_test_support::load_sse_fixture_with_id; use core_test_support::responses; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_function_call; @@ -24,10 +23,6 @@ use tokio::time::Duration; use tracing_subscriber::prelude::*; use uuid::Uuid; -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn new_thread_is_recorded_in_state_db() -> Result<()> { let server = start_mock_server().await; @@ -195,7 +190,10 @@ async fn user_messages_persist_in_state_db() -> Result<()> { let server = start_mock_server().await; mount_sse_sequence( &server, - vec![sse_completed("resp-1"), sse_completed("resp-2")], + vec![ + responses::sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + responses::sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), + ], ) .await; diff --git a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs index ec18816235..3562ec2500 100644 --- a/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs +++ b/codex-rs/core/tests/suite/stream_error_allows_next_turn.rs @@ -3,7 +3,9 @@ use codex_core::WireApi; use codex_core::protocol::EventMsg; use codex_core::protocol::Op; use codex_protocol::user_input::UserInput; -use core_test_support::load_sse_fixture_with_id; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; +use core_test_support::responses::sse; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; @@ -15,10 +17,6 @@ use wiremock::matchers::body_string_contains; use wiremock::matchers::method; use wiremock::matchers::path; -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn continue_after_stream_error() { skip_if_no_network!(); @@ -46,7 +44,13 @@ async fn continue_after_stream_error() { let ok = ResponseTemplate::new(200) .insert_header("content-type", "text/event-stream") - .set_body_raw(sse_completed("resp_ok2"), "text/event-stream"); + .set_body_raw( + sse(vec![ + ev_response_created("resp_ok2"), + ev_completed("resp_ok2"), + ]), + "text/event-stream", + ); Mock::given(method("POST")) .and(path("/v1/responses")) diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index 68b7763eca..e8b130970e 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -7,7 +7,9 @@ use codex_core::protocol::EventMsg; use codex_core::protocol::Op; use codex_protocol::user_input::UserInput; use core_test_support::load_sse_fixture; -use core_test_support::load_sse_fixture_with_id; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; +use core_test_support::responses::sse; use core_test_support::skip_if_no_network; use core_test_support::test_codex::TestCodex; use core_test_support::test_codex::test_codex; @@ -24,10 +26,6 @@ fn sse_incomplete() -> String { load_sse_fixture("tests/fixtures/incomplete_sse.json") } -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn retries_on_early_close() { skip_if_no_network!(); @@ -48,7 +46,13 @@ async fn retries_on_early_close() { } else { ResponseTemplate::new(200) .insert_header("content-type", "text/event-stream") - .set_body_raw(sse_completed("resp_ok"), "text/event-stream") + .set_body_raw( + sse(vec![ + ev_response_created("resp_ok"), + ev_completed("resp_ok"), + ]), + "text/event-stream", + ) } } } diff --git a/codex-rs/core/tests/suite/web_search.rs b/codex-rs/core/tests/suite/web_search.rs index 9397e88edb..8b1cc2a4e7 100644 --- a/codex-rs/core/tests/suite/web_search.rs +++ b/codex-rs/core/tests/suite/web_search.rs @@ -5,7 +5,6 @@ use codex_core::built_in_model_providers; use codex_core::features::Feature; use codex_core::protocol::SandboxPolicy; use codex_protocol::config_types::WebSearchMode; -use core_test_support::load_sse_fixture_with_id; use core_test_support::responses; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; @@ -13,10 +12,6 @@ use core_test_support::test_codex::test_codex; use pretty_assertions::assert_eq; use serde_json::Value; -fn sse_completed(id: &str) -> String { - load_sse_fixture_with_id("../fixtures/completed_template.json", id) -} - #[allow(clippy::expect_used)] fn find_web_search_tool(body: &Value) -> &Value { body["tools"] @@ -41,7 +36,10 @@ async fn web_search_mode_cached_sets_external_web_access_false() { skip_if_no_network!(); let server = start_mock_server().await; - let sse = sse_completed("resp-1"); + let sse = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); let resp_mock = responses::mount_sse_once(&server, sse).await; let mut builder = test_codex() @@ -72,7 +70,10 @@ async fn web_search_mode_takes_precedence_over_legacy_flags() { skip_if_no_network!(); let server = start_mock_server().await; - let sse = sse_completed("resp-1"); + let sse = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); let resp_mock = responses::mount_sse_once(&server, sse).await; let mut builder = test_codex() @@ -104,7 +105,10 @@ async fn web_search_mode_defaults_to_cached_when_unset() { skip_if_no_network!(); let server = start_mock_server().await; - let sse = sse_completed("resp-1"); + let sse = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); let resp_mock = responses::mount_sse_once(&server, sse).await; let mut builder = test_codex() @@ -139,7 +143,16 @@ async fn web_search_mode_updates_between_turns_with_sandbox_policy() { let server = start_mock_server().await; let resp_mock = responses::mount_sse_sequence( &server, - vec![sse_completed("resp-1"), sse_completed("resp-2")], + vec![ + responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]), + responses::sse(vec![ + responses::ev_response_created("resp-2"), + responses::ev_completed("resp-2"), + ]), + ], ) .await; @@ -191,7 +204,10 @@ async fn web_search_mode_defaults_to_disabled_for_azure_responses() { skip_if_no_network!(); let server = start_mock_server().await; - let sse = sse_completed("resp-1"); + let sse = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_completed("resp-1"), + ]); let resp_mock = responses::mount_sse_once(&server, sse).await; let mut builder = test_codex() From 49dd67a2606c73eed7316f882c8db027cbf71bbe Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 16:39:41 +0000 Subject: [PATCH 20/90] feat: land unified_exec (#10641) Land `unified_exec` for all non-windows OS --- codex-rs/core/src/config/mod.rs | 8 +- codex-rs/core/src/features.rs | 16 +-- codex-rs/core/src/tools/spec.rs | 40 +++++-- codex-rs/core/tests/suite/model_tools.rs | 126 ++++++++++++-------- codex-rs/core/tests/suite/prompt_caching.rs | 10 +- 5 files changed, 118 insertions(+), 82 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index d4547db19f..e22cfe65a1 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -3862,7 +3862,7 @@ model_verbosity = "high" forced_login_method: None, include_apply_patch_tool: false, web_search_mode: None, - use_experimental_unified_exec_tool: false, + use_experimental_unified_exec_tool: !cfg!(windows), ghost_snapshot: GhostSnapshotConfig::default(), features: Features::with_defaults(), suppress_unstable_features_warning: false, @@ -3947,7 +3947,7 @@ model_verbosity = "high" forced_login_method: None, include_apply_patch_tool: false, web_search_mode: None, - use_experimental_unified_exec_tool: false, + use_experimental_unified_exec_tool: !cfg!(windows), ghost_snapshot: GhostSnapshotConfig::default(), features: Features::with_defaults(), suppress_unstable_features_warning: false, @@ -4047,7 +4047,7 @@ model_verbosity = "high" forced_login_method: None, include_apply_patch_tool: false, web_search_mode: None, - use_experimental_unified_exec_tool: false, + use_experimental_unified_exec_tool: !cfg!(windows), ghost_snapshot: GhostSnapshotConfig::default(), features: Features::with_defaults(), suppress_unstable_features_warning: false, @@ -4133,7 +4133,7 @@ model_verbosity = "high" forced_login_method: None, include_apply_patch_tool: false, web_search_mode: None, - use_experimental_unified_exec_tool: false, + use_experimental_unified_exec_tool: !cfg!(windows), ghost_snapshot: GhostSnapshotConfig::default(), features: Features::with_defaults(), suppress_unstable_features_warning: false, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 1eb2a2dea5..a44477905e 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -406,6 +406,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Stable, default_enabled: true, }, + FeatureSpec { + id: Feature::UnifiedExec, + key: "unified_exec", + stage: Stage::Stable, + default_enabled: !cfg!(windows), + }, FeatureSpec { id: Feature::WebSearchRequest, key: "web_search_request", @@ -419,16 +425,6 @@ pub const FEATURES: &[FeatureSpec] = &[ default_enabled: false, }, // Experimental program. Rendered in the `/experimental` menu for users. - FeatureSpec { - id: Feature::UnifiedExec, - key: "unified_exec", - stage: Stage::Experimental { - name: "Background terminal", - menu_description: "Run long-running terminal commands in the background.", - announcement: "NEW! Try Background terminals for long-running commands. Enable in /experimental!", - }, - default_enabled: false, - }, FeatureSpec { id: Feature::ShellSnapshot, key: "shell_snapshot", diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 8851a157a2..ddac191a9b 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -1687,6 +1687,22 @@ mod tests { assert_eq!(&tool_names, &expected_tools,); } + fn assert_default_model_tools( + model_slug: &str, + features: &Features, + web_search_mode: Option, + shell_tool: &'static str, + expected_tail: &[&str], + ) { + let mut expected = if features.enabled(Feature::UnifiedExec) { + vec!["exec_command", "write_stdin"] + } else { + vec![shell_tool] + }; + expected.extend(expected_tail); + assert_model_tools(model_slug, features, web_search_mode, &expected); + } + #[test] fn web_search_mode_cached_sets_external_web_access_false() { let config = test_config(); @@ -1735,12 +1751,12 @@ mod tests { fn test_build_specs_gpt5_codex_default() { let mut features = Features::with_defaults(); features.enable(Feature::CollaborationModes); - assert_model_tools( + assert_default_model_tools( "gpt-5-codex", &features, Some(WebSearchMode::Cached), + "shell_command", &[ - "shell_command", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", @@ -1757,12 +1773,12 @@ mod tests { fn test_build_specs_gpt51_codex_default() { let mut features = Features::with_defaults(); features.enable(Feature::CollaborationModes); - assert_model_tools( + assert_default_model_tools( "gpt-5.1-codex", &features, Some(WebSearchMode::Cached), + "shell_command", &[ - "shell_command", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", @@ -1827,12 +1843,12 @@ mod tests { fn test_codex_mini_defaults() { let mut features = Features::with_defaults(); features.enable(Feature::CollaborationModes); - assert_model_tools( + assert_default_model_tools( "codex-mini-latest", &features, Some(WebSearchMode::Cached), + "local_shell", &[ - "local_shell", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", @@ -1848,12 +1864,12 @@ mod tests { fn test_codex_5_1_mini_defaults() { let mut features = Features::with_defaults(); features.enable(Feature::CollaborationModes); - assert_model_tools( + assert_default_model_tools( "gpt-5.1-codex-mini", &features, Some(WebSearchMode::Cached), + "shell_command", &[ - "shell_command", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", @@ -1870,12 +1886,12 @@ mod tests { fn test_gpt_5_defaults() { let mut features = Features::with_defaults(); features.enable(Feature::CollaborationModes); - assert_model_tools( + assert_default_model_tools( "gpt-5", &features, Some(WebSearchMode::Cached), + "shell", &[ - "shell", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", @@ -1891,12 +1907,12 @@ mod tests { fn test_gpt_5_1_defaults() { let mut features = Features::with_defaults(); features.enable(Feature::CollaborationModes); - assert_model_tools( + assert_default_model_tools( "gpt-5.1", &features, Some(WebSearchMode::Cached), + "shell_command", &[ - "shell_command", "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", diff --git a/codex-rs/core/tests/suite/model_tools.rs b/codex-rs/core/tests/suite/model_tools.rs index c687545f26..d74ab0d7be 100644 --- a/codex-rs/core/tests/suite/model_tools.rs +++ b/codex-rs/core/tests/suite/model_tools.rs @@ -50,6 +50,16 @@ async fn collect_tool_identifiers_for_model(model: &str) -> Vec { tool_identifiers(&body) } +fn expected_default_tools(shell_tool: &str, tail: &[&str]) -> Vec { + let mut tools = if cfg!(windows) { + vec![shell_tool.to_string()] + } else { + vec!["exec_command".to_string(), "write_stdin".to_string()] + }; + tools.extend(tail.iter().map(|tool| (*tool).to_string())); + tools +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn model_selects_expected_tools() { skip_if_no_network!(); @@ -58,83 +68,93 @@ async fn model_selects_expected_tools() { let codex_tools = collect_tool_identifiers_for_model("codex-mini-latest").await; assert_eq!( codex_tools, - vec![ - "local_shell".to_string(), - "list_mcp_resources".to_string(), - "list_mcp_resource_templates".to_string(), - "read_mcp_resource".to_string(), - "update_plan".to_string(), - "request_user_input".to_string(), - "web_search".to_string(), - "view_image".to_string() - ], + expected_default_tools( + "local_shell", + &[ + "list_mcp_resources", + "list_mcp_resource_templates", + "read_mcp_resource", + "update_plan", + "request_user_input", + "web_search", + "view_image", + ], + ), "codex-mini-latest should expose the local shell tool", ); let gpt5_codex_tools = collect_tool_identifiers_for_model("gpt-5-codex").await; assert_eq!( gpt5_codex_tools, - vec![ - "shell_command".to_string(), - "list_mcp_resources".to_string(), - "list_mcp_resource_templates".to_string(), - "read_mcp_resource".to_string(), - "update_plan".to_string(), - "request_user_input".to_string(), - "apply_patch".to_string(), - "web_search".to_string(), - "view_image".to_string() - ], + expected_default_tools( + "shell_command", + &[ + "list_mcp_resources", + "list_mcp_resource_templates", + "read_mcp_resource", + "update_plan", + "request_user_input", + "apply_patch", + "web_search", + "view_image", + ], + ), "gpt-5-codex should expose the apply_patch tool", ); let gpt51_codex_tools = collect_tool_identifiers_for_model("gpt-5.1-codex").await; assert_eq!( gpt51_codex_tools, - vec![ - "shell_command".to_string(), - "list_mcp_resources".to_string(), - "list_mcp_resource_templates".to_string(), - "read_mcp_resource".to_string(), - "update_plan".to_string(), - "request_user_input".to_string(), - "apply_patch".to_string(), - "web_search".to_string(), - "view_image".to_string() - ], + expected_default_tools( + "shell_command", + &[ + "list_mcp_resources", + "list_mcp_resource_templates", + "read_mcp_resource", + "update_plan", + "request_user_input", + "apply_patch", + "web_search", + "view_image", + ], + ), "gpt-5.1-codex should expose the apply_patch tool", ); let gpt5_tools = collect_tool_identifiers_for_model("gpt-5").await; assert_eq!( gpt5_tools, - vec![ - "shell".to_string(), - "list_mcp_resources".to_string(), - "list_mcp_resource_templates".to_string(), - "read_mcp_resource".to_string(), - "update_plan".to_string(), - "request_user_input".to_string(), - "web_search".to_string(), - "view_image".to_string() - ], + expected_default_tools( + "shell", + &[ + "list_mcp_resources", + "list_mcp_resource_templates", + "read_mcp_resource", + "update_plan", + "request_user_input", + "web_search", + "view_image", + ], + ), "gpt-5 should expose the apply_patch tool", ); let gpt51_tools = collect_tool_identifiers_for_model("gpt-5.1").await; assert_eq!( gpt51_tools, - vec![ - "shell_command".to_string(), - "list_mcp_resources".to_string(), - "list_mcp_resource_templates".to_string(), - "read_mcp_resource".to_string(), - "update_plan".to_string(), - "request_user_input".to_string(), - "apply_patch".to_string(), - "web_search".to_string(), - "view_image".to_string() - ], + expected_default_tools( + "shell_command", + &[ + "list_mcp_resources", + "list_mcp_resource_templates", + "read_mcp_resource", + "update_plan", + "request_user_input", + "apply_patch", + "web_search", + "view_image", + ], + ), "gpt-5.1 should expose the apply_patch tool", ); let exp_tools = collect_tool_identifiers_for_model("exp-5.1").await; diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 810cd52497..acff97381a 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -136,8 +136,12 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { .await?; wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; - let expected_tools_names = vec![ - "shell_command", + let mut expected_tools_names = if cfg!(windows) { + vec!["shell_command"] + } else { + vec!["exec_command", "write_stdin"] + }; + expected_tools_names.extend([ "list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource", @@ -146,7 +150,7 @@ async fn prompt_tools_are_consistent_across_requests() -> anyhow::Result<()> { "apply_patch", "web_search", "view_image", - ]; + ]); let body0 = req1.single_request().body_json(); let expected_instructions = if expected_tools_names.contains(&"apply_patch") { From 7f203576116de4002fcda5942a1e4248e18692ff Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Wed, 4 Feb 2026 09:05:37 -0800 Subject: [PATCH 21/90] Stop client from being state carrier (#10595) I'd like to make client session wide. This requires shedding all random state it has to carry. --- codex-rs/core/src/client.rs | 55 -------- codex-rs/core/src/codex.rs | 133 ++++++++++++------- codex-rs/core/src/compact.rs | 10 +- codex-rs/core/src/compact_remote.rs | 4 +- codex-rs/core/src/context_manager/history.rs | 6 +- codex-rs/core/src/mcp/skill_dependencies.rs | 2 +- codex-rs/core/src/mcp_tool_call.rs | 6 +- codex-rs/core/src/stream_events_utils.rs | 3 +- codex-rs/core/src/tasks/compact.rs | 5 +- codex-rs/core/src/tasks/mod.rs | 3 +- codex-rs/core/src/tasks/review.rs | 4 +- codex-rs/core/src/tasks/user_shell.rs | 2 +- codex-rs/core/src/tools/handlers/collab.rs | 66 +++------ codex-rs/core/src/tools/orchestrator.rs | 2 +- codex-rs/core/src/tools/registry.rs | 2 +- 15 files changed, 127 insertions(+), 176 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 1d0dc7fb65..dd92c5da01 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -153,61 +153,6 @@ impl ModelClient { } impl ModelClient { - pub fn get_model_context_window(&self) -> Option { - let model_info = &self.state.model_info; - let effective_context_window_percent = model_info.effective_context_window_percent; - model_info.context_window.map(|context_window| { - context_window.saturating_mul(effective_context_window_percent) / 100 - }) - } - - pub fn config(&self) -> Arc { - Arc::clone(&self.state.config) - } - - pub fn provider(&self) -> &ModelProviderInfo { - &self.state.provider - } - - pub fn get_provider(&self) -> ModelProviderInfo { - self.state.provider.clone() - } - - pub fn get_otel_manager(&self) -> OtelManager { - self.state.otel_manager.clone() - } - - pub fn get_session_source(&self) -> SessionSource { - self.state.session_source.clone() - } - - pub(crate) fn transport_manager(&self) -> TransportManager { - self.state.transport_manager.clone() - } - - /// Returns the currently configured model slug. - pub fn get_model(&self) -> String { - self.state.model_info.slug.clone() - } - - pub fn get_model_info(&self) -> ModelInfo { - self.state.model_info.clone() - } - - /// Returns the current reasoning effort setting. - pub fn get_reasoning_effort(&self) -> Option { - self.state.effort - } - - /// Returns the current reasoning summary setting. - pub fn get_reasoning_summary(&self) -> ReasoningSummaryConfig { - self.state.summary - } - - pub fn get_auth_manager(&self) -> Option> { - self.state.auth_manager.clone() - } - /// Compacts the current conversation history using the Compact endpoint. /// /// This is a unary call (no streaming) that returns a new list of diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 7fe3b8c810..979f2a5f62 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -212,6 +212,7 @@ use codex_protocol::models::ContentItem; use codex_protocol::models::DeveloperInstructions; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::InitialHistory; use codex_protocol::user_input::UserInput; @@ -484,6 +485,15 @@ pub(crate) struct Session { pub(crate) struct TurnContext { pub(crate) sub_id: String, pub(crate) client: ModelClient, + pub(crate) config: Arc, + pub(crate) auth_manager: Option>, + pub(crate) model_info: ModelInfo, + pub(crate) otel_manager: OtelManager, + pub(crate) provider: ModelProviderInfo, + pub(crate) reasoning_effort: Option, + pub(crate) reasoning_summary: ReasoningSummaryConfig, + pub(crate) session_source: SessionSource, + pub(crate) transport_manager: TransportManager, /// The session's current working directory. All relative paths provided by /// the model as well as sandbox policies are resolved against this path /// instead of `std::env::current_dir()`. @@ -507,6 +517,13 @@ pub(crate) struct TurnContext { turn_metadata_header: OnceCell>, } impl TurnContext { + pub(crate) fn model_context_window(&self) -> Option { + let effective_context_window_percent = self.model_info.effective_context_window_percent; + self.model_info.context_window.map(|context_window| { + context_window.saturating_mul(effective_context_window_percent) / 100 + }) + } + pub(crate) fn resolve_path(&self, path: Option) -> PathBuf { path.as_ref() .map(PathBuf::from) @@ -694,10 +711,17 @@ impl Session { sub_id: String, transport_manager: TransportManager, ) -> TurnContext { + let reasoning_effort = session_configuration.collaboration_mode.reasoning_effort(); + let reasoning_summary = session_configuration.model_reasoning_summary; let otel_manager = otel_manager.clone().with_model( session_configuration.collaboration_mode.model(), model_info.slug.as_str(), ); + let session_source = session_configuration.session_source.clone(); + let auth_manager_for_context = auth_manager.clone(); + let provider_for_context = provider.clone(); + let transport_manager_for_context = transport_manager.clone(); + let otel_manager_for_context = otel_manager.clone(); let per_turn_config = Arc::new(per_turn_config); let client = ModelClient::new( per_turn_config.clone(), @@ -705,10 +729,10 @@ impl Session { model_info.clone(), otel_manager, provider, - session_configuration.collaboration_mode.reasoning_effort(), - session_configuration.model_reasoning_summary, + reasoning_effort, + reasoning_summary, conversation_id, - session_configuration.session_source.clone(), + session_source.clone(), transport_manager, ); @@ -722,6 +746,15 @@ impl Session { TurnContext { sub_id, client, + config: per_turn_config.clone(), + auth_manager: auth_manager_for_context, + model_info: model_info.clone(), + otel_manager: otel_manager_for_context, + provider: provider_for_context, + reasoning_effort, + reasoning_summary, + session_source, + transport_manager: transport_manager_for_context, cwd, developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), @@ -1100,7 +1133,7 @@ impl Session { None } }) { - let curr = turn_context.client.get_model(); + let curr = turn_context.model_info.slug.as_str(); if prev != curr { warn!( "resuming session with different model: previous={prev}, current={curr}" @@ -1378,8 +1411,8 @@ impl Session { if let Some(personality) = next.personality && next.personality != previous.personality { - let model_info = next.client.get_model_info(); - let personality_message = Self::personality_message_for(&model_info, personality); + let model_info = &next.model_info; + let personality_message = Self::personality_message_for(model_info, personality); personality_message.map(|personality_message| { DeveloperInstructions::personality_spec_message(personality_message).into() }) @@ -1943,7 +1976,7 @@ impl Session { if self.features.enabled(Feature::Personality) && let Some(personality) = turn_context.personality { - let model_info = turn_context.client.get_model_info(); + let model_info = turn_context.model_info.clone(); let has_baked_personality = model_info.supports_personality() && base_instructions == model_info.get_model_instructions(Some(personality)); if !has_baked_personality @@ -1996,10 +2029,8 @@ impl Session { { let mut state = self.state.lock().await; if let Some(token_usage) = token_usage { - state.update_token_info_from_usage( - token_usage, - turn_context.client.get_model_context_window(), - ); + state + .update_token_info_from_usage(token_usage, turn_context.model_context_window()); } } self.send_token_count_event(turn_context).await; @@ -2030,7 +2061,7 @@ impl Session { }; if info.model_context_window.is_none() { - info.model_context_window = turn_context.client.get_model_context_window(); + info.model_context_window = turn_context.model_context_window(); } state.set_token_info(Some(info)); @@ -2088,7 +2119,7 @@ impl Session { } pub(crate) async fn set_total_tokens_full(&self, turn_context: &TurnContext) { - if let Some(context_window) = turn_context.client.get_model_context_window() { + if let Some(context_window) = turn_context.model_context_window() { let mut state = self.state.lock().await; state.set_token_usage_full(context_window); } @@ -2702,10 +2733,7 @@ mod handlers { // new_turn_with_sub_id already emits the error event. return; }; - current_context - .client - .get_otel_manager() - .user_prompt(&items); + current_context.otel_manager.user_prompt(&items); // Attempt to inject input into current task if let Err(items) = sess.inject_input(items).await { @@ -3247,7 +3275,7 @@ async fn spawn_review_thread( let model = config .review_model .clone() - .unwrap_or_else(|| parent_turn_context.client.get_model()); + .unwrap_or_else(|| parent_turn_context.model_info.slug.clone()); let review_model_info = sess .services .models_manager @@ -3266,8 +3294,8 @@ async fn spawn_review_thread( }); let review_prompt = resolved.prompt.clone(); - let provider = parent_turn_context.client.get_provider(); - let auth_manager = parent_turn_context.client.get_auth_manager(); + let provider = parent_turn_context.provider.clone(); + let auth_manager = parent_turn_context.auth_manager.clone(); let model_info = review_model_info.clone(); // Build per‑turn client with the requested model/family. @@ -3277,9 +3305,16 @@ async fn spawn_review_thread( per_turn_config.web_search_mode = Some(review_web_search_mode); let otel_manager = parent_turn_context - .client - .get_otel_manager() + .otel_manager + .clone() .with_model(model.as_str(), review_model_info.slug.as_str()); + let auth_manager_for_context = auth_manager.clone(); + let provider_for_context = provider.clone(); + let otel_manager_for_context = otel_manager.clone(); + let reasoning_effort = per_turn_config.model_reasoning_effort; + let reasoning_summary = per_turn_config.model_reasoning_summary; + let session_source = parent_turn_context.session_source.clone(); + let transport_manager = parent_turn_context.transport_manager.clone(); let per_turn_config = Arc::new(per_turn_config); let client = ModelClient::new( @@ -3288,16 +3323,25 @@ async fn spawn_review_thread( model_info.clone(), otel_manager, provider, - per_turn_config.model_reasoning_effort, - per_turn_config.model_reasoning_summary, + reasoning_effort, + reasoning_summary, sess.conversation_id, - parent_turn_context.client.get_session_source(), - parent_turn_context.client.transport_manager(), + session_source.clone(), + transport_manager.clone(), ); let review_turn_context = TurnContext { sub_id: sub_id.to_string(), client, + config: per_turn_config, + auth_manager: auth_manager_for_context, + model_info: model_info.clone(), + otel_manager: otel_manager_for_context, + provider: provider_for_context, + reasoning_effort, + reasoning_summary, + session_source, + transport_manager, tools_config, ghost_snapshot: parent_turn_context.ghost_snapshot.clone(), developer_instructions: None, @@ -3414,12 +3458,12 @@ pub(crate) async fn run_turn( return None; } - let model_info = turn_context.client.get_model_info(); + let model_info = turn_context.model_info.clone(); 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(), + model_context_window: turn_context.model_context_window(), collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, event).await; @@ -3438,7 +3482,7 @@ pub(crate) async fn run_turn( || (HashMap::new(), HashMap::new()), |outcome| build_skill_name_counts(&outcome.skills, &outcome.disabled_paths), ); - let connector_slug_counts = if turn_context.client.config().features.enabled(Feature::Apps) { + let connector_slug_counts = if turn_context.config.features.enabled(Feature::Apps) { let mcp_tools = match sess .services .mcp_connection_manager @@ -3467,7 +3511,7 @@ pub(crate) async fn run_turn( }); let explicit_app_paths = collect_explicit_app_paths(&input); - let config = turn_context.client.config(); + let config = turn_context.config.clone(); if config .features .enabled(Feature::SkillEnvVarDependencyPrompt) @@ -3484,9 +3528,9 @@ pub(crate) async fn run_turn( ) .await; - let otel_manager = turn_context.client.get_otel_manager(); + let otel_manager = turn_context.otel_manager.clone(); let thread_id = sess.conversation_id.to_string(); - let tracking = build_track_events_context(turn_context.client.get_model(), thread_id); + let tracking = build_track_events_context(turn_context.model_info.slug.clone(), thread_id); let SkillInjections { items: skill_items, warnings: skill_warnings, @@ -3639,7 +3683,7 @@ pub(crate) async fn run_turn( } async fn run_auto_compact(sess: &Arc, turn_context: &Arc) { - if should_use_remote_compact_task(sess.as_ref(), &turn_context.client.get_provider()) { + if should_use_remote_compact_task(sess.as_ref(), &turn_context.provider) { run_inline_remote_auto_compact_task(Arc::clone(sess), Arc::clone(turn_context)).await; } else { run_inline_auto_compact_task(Arc::clone(sess), Arc::clone(turn_context)).await; @@ -3748,7 +3792,7 @@ struct SamplingRequestToolSelection<'a> { skip_all, fields( turn_id = %turn_context.sub_id, - model = %turn_context.client.get_model(), + model = %turn_context.model_info.slug, cwd = %turn_context.cwd.display() ) )] @@ -3769,7 +3813,7 @@ async fn run_sampling_request( .list_all_tools() .or_cancel(&cancellation_token) .await?; - let connectors_for_tools = if turn_context.client.config().features.enabled(Feature::Apps) { + let connectors_for_tools = if turn_context.config.features.enabled(Feature::Apps) { let connectors = connectors::accessible_connectors_from_mcp_tools(&mcp_tools); Some(filter_connectors_for_input( connectors, @@ -3794,10 +3838,7 @@ async fn run_sampling_request( turn_context.dynamic_tools.as_slice(), )); - let model_supports_parallel = turn_context - .client - .get_model_info() - .supports_parallel_tool_calls; + let model_supports_parallel = turn_context.model_info.supports_parallel_tool_calls; let base_instructions = sess.get_base_instructions().await; @@ -3845,7 +3886,7 @@ async fn run_sampling_request( } // Use the configured provider-specific stream retry budget. - let max_retries = turn_context.client.get_provider().stream_max_retries(); + let max_retries = turn_context.provider.stream_max_retries(); if retries >= max_retries && client_session.try_switch_fallback_transport() { sess.send_event( &turn_context, @@ -4291,7 +4332,7 @@ async fn drain_in_flight( skip_all, fields( turn_id = %turn_context.sub_id, - model = %turn_context.client.get_model() + model = %turn_context.model_info.slug ) )] async fn try_run_sampling_request( @@ -4308,11 +4349,11 @@ async fn try_run_sampling_request( cwd: turn_context.cwd.clone(), approval_policy: turn_context.approval_policy, sandbox_policy: turn_context.sandbox_policy.clone(), - model: turn_context.client.get_model(), + model: turn_context.model_info.slug.clone(), personality: turn_context.personality, collaboration_mode: Some(collaboration_mode), - effort: turn_context.client.get_reasoning_effort(), - summary: turn_context.client.get_reasoning_summary(), + effort: turn_context.reasoning_effort, + summary: turn_context.reasoning_summary, user_instructions: turn_context.user_instructions.clone(), developer_instructions: turn_context.developer_instructions.clone(), final_output_json_schema: turn_context.final_output_json_schema.clone(), @@ -4320,10 +4361,10 @@ async fn try_run_sampling_request( }); feedback_tags!( - model = turn_context.client.get_model(), + model = turn_context.model_info.slug.clone(), approval_policy = turn_context.approval_policy, sandbox_policy = turn_context.sandbox_policy, - effort = turn_context.client.get_reasoning_effort(), + effort = turn_context.reasoning_effort, auth_mode = sess.services.auth_manager.get_auth_mode(), features = sess.features.enabled_features(), ); diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 7ae773884c..c8aba56613 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -60,7 +60,7 @@ pub(crate) async fn run_compact_task( input: Vec, ) { let start_event = EventMsg::TurnStarted(TurnStartedEvent { - model_context_window: turn_context.client.get_model_context_window(), + model_context_window: turn_context.model_context_window(), collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, start_event).await; @@ -85,7 +85,7 @@ async fn run_compact_task_inner( let mut truncated_count = 0usize; - let max_retries = turn_context.client.get_provider().stream_max_retries(); + let max_retries = turn_context.provider.stream_max_retries(); let mut retries = 0; // TODO: If we need to guarantee the persisted mode always matches the prompt used for this @@ -97,11 +97,11 @@ async fn run_compact_task_inner( cwd: turn_context.cwd.clone(), approval_policy: turn_context.approval_policy, sandbox_policy: turn_context.sandbox_policy.clone(), - model: turn_context.client.get_model(), + model: turn_context.model_info.slug.clone(), personality: turn_context.personality, collaboration_mode: Some(collaboration_mode), - effort: turn_context.client.get_reasoning_effort(), - summary: turn_context.client.get_reasoning_summary(), + effort: turn_context.reasoning_effort, + summary: turn_context.reasoning_summary, user_instructions: turn_context.user_instructions.clone(), developer_instructions: turn_context.developer_instructions.clone(), final_output_json_schema: turn_context.final_output_json_schema.clone(), diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index 12bc769ce2..fbb6863c37 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -24,7 +24,7 @@ pub(crate) async fn run_inline_remote_auto_compact_task( pub(crate) async fn run_remote_compact_task(sess: Arc, turn_context: Arc) { let start_event = EventMsg::TurnStarted(TurnStartedEvent { - model_context_window: turn_context.client.get_model_context_window(), + model_context_window: turn_context.model_context_window(), collaboration_mode_kind: turn_context.collaboration_mode.mode, }); sess.send_event(&turn_context, start_event).await; @@ -104,7 +104,7 @@ fn trim_function_call_history_to_fit_context_window( turn_context: &TurnContext, ) -> usize { let mut deleted_items = 0usize; - let Some(context_window) = turn_context.client.get_model_context_window() else { + let Some(context_window) = turn_context.model_context_window() else { return deleted_items; }; diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index a29f7df7e0..a68c4daafe 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -85,10 +85,8 @@ impl ContextManager { // Estimate token usage using byte-based heuristics from the truncation helpers. // This is a coarse lower bound, not a tokenizer-accurate count. pub(crate) fn estimate_token_count(&self, turn_context: &TurnContext) -> Option { - let model_info = turn_context.client.get_model_info(); - let personality = turn_context - .personality - .or(turn_context.client.config().personality); + let model_info = &turn_context.model_info; + let personality = turn_context.personality.or(turn_context.config.personality); let base_instructions = model_info.get_model_instructions(personality); let base_tokens = i64::try_from(approx_token_count(&base_instructions)).unwrap_or(i64::MAX); diff --git a/codex-rs/core/src/mcp/skill_dependencies.rs b/codex-rs/core/src/mcp/skill_dependencies.rs index 37620d73db..14b71fcf19 100644 --- a/codex-rs/core/src/mcp/skill_dependencies.rs +++ b/codex-rs/core/src/mcp/skill_dependencies.rs @@ -144,7 +144,7 @@ pub(crate) async fn maybe_prompt_and_install_mcp_dependencies( return; } - let config = turn_context.client.config(); + let config = turn_context.config.clone(); if mentioned_skills.is_empty() || !config.features.enabled(Feature::SkillMcpDependencyInstall) { return; } diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 75248f34cc..d1d009470d 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -120,8 +120,7 @@ pub(crate) async fn handle_mcp_tool_call( let status = if result.is_ok() { "ok" } else { "error" }; turn_context - .client - .get_otel_manager() + .otel_manager .counter("codex.mcp.call", 1, &[("status", status)]); return ResponseInputItem::McpToolCallOutput { call_id, result }; @@ -153,8 +152,7 @@ pub(crate) async fn handle_mcp_tool_call( let status = if result.is_ok() { "ok" } else { "error" }; turn_context - .client - .get_otel_manager() + .otel_manager .counter("codex.mcp.call", 1, &[("status", status)]); ResponseInputItem::McpToolCallOutput { call_id, result } diff --git a/codex-rs/core/src/stream_events_utils.rs b/codex-rs/core/src/stream_events_utils.rs index 02d9822510..31b4f18372 100644 --- a/codex-rs/core/src/stream_events_utils.rs +++ b/codex-rs/core/src/stream_events_utils.rs @@ -100,8 +100,7 @@ pub(crate) async fn handle_output_item_done( Err(FunctionCallError::MissingLocalShellCallId) => { let msg = "LocalShellCall without call_id or id"; ctx.turn_context - .client - .get_otel_manager() + .otel_manager .log_tool_failed("local_shell", msg); tracing::error!(msg); diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index 00d882f607..e1fdbfd91f 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -25,10 +25,7 @@ impl SessionTask for CompactTask { _cancellation_token: CancellationToken, ) -> Option { let session = session.clone_session(); - if crate::compact::should_use_remote_compact_task( - session.as_ref(), - &ctx.client.get_provider(), - ) { + if crate::compact::should_use_remote_compact_task(session.as_ref(), &ctx.provider) { let _ = session.services.otel_manager.counter( "codex.task.compact", 1, diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index d5d6f60582..0ab6e2f49b 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -158,8 +158,7 @@ impl Session { }; let timer = turn_context - .client - .get_otel_manager() + .otel_manager .start_timer("codex.turn.e2e_duration_ms", &[]) .ok(); diff --git a/codex-rs/core/src/tasks/review.rs b/codex-rs/core/src/tasks/review.rs index cfc554f856..87d6c41933 100644 --- a/codex-rs/core/src/tasks/review.rs +++ b/codex-rs/core/src/tasks/review.rs @@ -82,7 +82,7 @@ async fn start_review_conversation( input: Vec, cancellation_token: CancellationToken, ) -> Option> { - let config = ctx.client.config(); + let config = ctx.config.clone(); let mut sub_agent_config = config.as_ref().clone(); // Carry over review-only feature restrictions so the delegate cannot // re-enable blocked tools (web search, view image). @@ -94,7 +94,7 @@ async fn start_review_conversation( let model = config .review_model .clone() - .unwrap_or_else(|| ctx.client.get_model()); + .unwrap_or_else(|| ctx.model_info.slug.clone()); sub_agent_config.model = Some(model); (run_codex_thread_one_shot( sub_agent_config, diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index a3d38afd2c..6c37cb150e 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -66,7 +66,7 @@ impl SessionTask for UserShellCommandTask { .counter("codex.task.user_shell", 1, &[]); let event = EventMsg::TurnStarted(TurnStartedEvent { - model_context_window: turn_context.client.get_model_context_window(), + model_context_window: turn_context.model_context_window(), collaboration_mode_kind: turn_context.collaboration_mode.mode, }); let session = session.clone_session(); diff --git a/codex-rs/core/src/tools/handlers/collab.rs b/codex-rs/core/src/tools/handlers/collab.rs index 61ccc1932e..7ef19506d5 100644 --- a/codex-rs/core/src/tools/handlers/collab.rs +++ b/codex-rs/core/src/tools/handlers/collab.rs @@ -114,7 +114,7 @@ mod spawn { "Empty message can't be sent to an agent".to_string(), )); } - let session_source = turn.client.get_session_source(); + let session_source = turn.session_source.clone(); let child_depth = next_thread_spawn_depth(&session_source); if exceeds_thread_spawn_depth_limit(child_depth) { return Err(FunctionCallError::RespondToModel( @@ -593,13 +593,13 @@ fn build_agent_spawn_config( turn: &TurnContext, child_depth: i32, ) -> Result { - let base_config = turn.client.config(); + let base_config = turn.config.clone(); let mut config = (*base_config).clone(); config.base_instructions = Some(base_instructions.text.clone()); - config.model = Some(turn.client.get_model()); - config.model_provider = turn.client.get_provider(); - config.model_reasoning_effort = turn.client.get_reasoning_effort(); - config.model_reasoning_summary = turn.client.get_reasoning_summary(); + config.model = Some(turn.model_info.slug.clone()); + config.model_provider = turn.provider.clone(); + config.model_reasoning_effort = turn.reasoning_effort; + config.model_reasoning_summary = turn.reasoning_summary; config.developer_instructions = turn.developer_instructions.clone(); config.compact_prompt = turn.compact_prompt.clone(); config.shell_environment_policy = turn.shell_environment_policy.clone(); @@ -633,7 +633,6 @@ mod tests { use crate::ThreadManager; use crate::agent::MAX_THREAD_SPAWN_DEPTH; use crate::built_in_model_providers; - use crate::client::ModelClient; use crate::codex::make_session_and_context; use crate::config::types::ShellEnvironmentPolicy; use crate::function_tool::FunctionCallError; @@ -767,22 +766,10 @@ mod tests { let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn { + turn.session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id: session.conversation_id, depth: MAX_THREAD_SPAWN_DEPTH, }); - turn.client = ModelClient::new( - turn.client.config(), - Some(session.services.auth_manager.clone()), - turn.client.get_model_info(), - turn.client.get_otel_manager(), - turn.client.get_provider(), - turn.client.get_reasoning_effort(), - turn.client.get_reasoning_summary(), - session.conversation_id, - session_source, - session.services.transport_manager.clone(), - ); let invocation = invocation( Arc::new(session), @@ -865,7 +852,7 @@ mod tests { let (mut session, turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let config = turn.client.config().as_ref().clone(); + let config = turn.config.as_ref().clone(); let thread = manager.start_thread(config).await.expect("start thread"); let agent_id = thread.thread_id; let invocation = invocation( @@ -1008,7 +995,7 @@ mod tests { let (mut session, turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let config = turn.client.config().as_ref().clone(); + let config = turn.config.as_ref().clone(); let thread = manager.start_thread(config).await.expect("start thread"); let agent_id = thread.thread_id; let invocation = invocation( @@ -1053,7 +1040,7 @@ mod tests { let (mut session, turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let config = turn.client.config().as_ref().clone(); + let config = turn.config.as_ref().clone(); let thread = manager.start_thread(config).await.expect("start thread"); let agent_id = thread.thread_id; let invocation = invocation( @@ -1084,7 +1071,7 @@ mod tests { let (mut session, turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let config = turn.client.config().as_ref().clone(); + let config = turn.config.as_ref().clone(); let thread = manager.start_thread(config).await.expect("start thread"); let agent_id = thread.thread_id; let mut status_rx = manager @@ -1138,7 +1125,7 @@ mod tests { let (mut session, turn) = make_session_and_context().await; let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let config = turn.client.config().as_ref().clone(); + let config = turn.config.as_ref().clone(); let thread = manager.start_thread(config).await.expect("start thread"); let agent_id = thread.thread_id; let status_before = manager.agent_control().get_status(agent_id).await; @@ -1193,12 +1180,12 @@ mod tests { turn.sandbox_policy = SandboxPolicy::DangerFullAccess; let config = build_agent_spawn_config(&base_instructions, &turn, 0).expect("spawn config"); - let mut expected = (*turn.client.config()).clone(); + let mut expected = (*turn.config).clone(); expected.base_instructions = Some(base_instructions.text); - expected.model = Some(turn.client.get_model()); - expected.model_provider = turn.client.get_provider(); - expected.model_reasoning_effort = turn.client.get_reasoning_effort(); - expected.model_reasoning_summary = turn.client.get_reasoning_summary(); + expected.model = Some(turn.model_info.slug.clone()); + expected.model_provider = turn.provider.clone(); + expected.model_reasoning_effort = turn.reasoning_effort; + expected.model_reasoning_summary = turn.reasoning_summary; expected.developer_instructions = turn.developer_instructions.clone(); expected.compact_prompt = turn.compact_prompt.clone(); expected.shell_environment_policy = turn.shell_environment_policy.clone(); @@ -1217,24 +1204,11 @@ mod tests { #[tokio::test] async fn build_agent_spawn_config_preserves_base_user_instructions() { - let (session, mut turn) = make_session_and_context().await; - let session_source = turn.client.get_session_source(); - let mut base_config = (*turn.client.config()).clone(); + let (_session, mut turn) = make_session_and_context().await; + let mut base_config = (*turn.config).clone(); base_config.user_instructions = Some("base-user".to_string()); turn.user_instructions = Some("resolved-user".to_string()); - let transport_manager = turn.client.transport_manager(); - turn.client = ModelClient::new( - Arc::new(base_config.clone()), - Some(session.services.auth_manager.clone()), - turn.client.get_model_info(), - turn.client.get_otel_manager(), - turn.client.get_provider(), - turn.client.get_reasoning_effort(), - turn.client.get_reasoning_summary(), - session.conversation_id, - session_source, - transport_manager, - ); + turn.config = Arc::new(base_config.clone()); let base_instructions = BaseInstructions { text: "base".to_string(), }; diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index e9fdd6208b..fb2150953b 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -43,7 +43,7 @@ impl ToolOrchestrator { where T: ToolRuntime, { - let otel = turn_ctx.client.get_otel_manager(); + let otel = turn_ctx.otel_manager.clone(); let otel_tn = &tool_ctx.tool_name; let otel_ci = &tool_ctx.call_id; let otel_user = ToolDecisionSource::User; diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index 72b848e315..f67db63943 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -70,7 +70,7 @@ impl ToolRegistry { ) -> Result { let tool_name = invocation.tool_name.clone(); let call_id_owned = invocation.call_id.clone(); - let otel = invocation.turn.client.get_otel_manager(); + let otel = invocation.turn.otel_manager.clone(); let payload_for_response = invocation.payload.clone(); let log_payload = payload_for_response.log_payload(); From 282f42c0ce3c067a1a00c7590f76ef7bec47e207 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Wed, 4 Feb 2026 09:38:41 -0800 Subject: [PATCH 22/90] Add option to approve and remember MCP/Apps tool usage (#10584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a new approval option for app/MCP tool calls: “Allow and remember” (session-scoped). When selected, Codex stores a temporary approval and auto-approves matching future calls for the rest of the session. Added a session-scoped approval key (`server`, `connector_id`, `tool_name`) and persisted it in `tool_approvals` as `ApprovedForSession`. On subsequent matching calls, approval is skipped and treated as accepted. - Updated the approval question options to conditionally include: - Accept - Allow and remember (conditional) - Decline - Cancel The new “Allow and remember” option is only shown when all of these are true: 1. The call is routed through the Codex Apps MCP server (codex_apps). 2. The tool requires approval based on annotations: - read_only_hint == false, and - destructive_hint == true or open_world_hint == true. 3. The tool includes a connector_id in metadata (used to build the remembered approval key). If no `connector_id` is present, the prompt still appears (when approval is required), but only with the existing choices (Accept / Decline / Cancel). Approval prompting in this path has an explicit early return unless server == `codex_apps`. --- codex-rs/core/src/mcp_tool_call.rs | 93 ++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index d1d009470d..0c9a4a0033 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -14,12 +14,14 @@ use codex_protocol::mcp::CallToolResult; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::protocol::AskForApproval; +use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::request_user_input::RequestUserInputArgs; use codex_protocol::request_user_input::RequestUserInputQuestion; use codex_protocol::request_user_input::RequestUserInputQuestionOption; use codex_protocol::request_user_input::RequestUserInputResponse; use rmcp::model::ToolAnnotations; +use serde::Serialize; use std::sync::Arc; /// Handles the specified tool call dispatches the appropriate @@ -64,7 +66,7 @@ pub(crate) async fn handle_mcp_tool_call( .await { let result = match decision { - McpToolApprovalDecision::Accept => { + McpToolApprovalDecision::Accept | McpToolApprovalDecision::AcceptAndRemember => { let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent { call_id: call_id.clone(), invocation: invocation.clone(), @@ -165,21 +167,31 @@ async fn notify_mcp_tool_call_event(sess: &Session, turn_context: &TurnContext, #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum McpToolApprovalDecision { Accept, + AcceptAndRemember, Decline, Cancel, } struct McpToolApprovalMetadata { annotations: ToolAnnotations, + connector_id: Option, connector_name: Option, tool_title: Option, } const MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX: &str = "mcp_tool_call_approval"; -const MCP_TOOL_APPROVAL_ACCEPT: &str = "Accept"; -const MCP_TOOL_APPROVAL_DECLINE: &str = "Decline"; +const MCP_TOOL_APPROVAL_ACCEPT: &str = "Approve Once"; +const MCP_TOOL_APPROVAL_ACCEPT_AND_REMEMBER: &str = "Approve this Session"; +const MCP_TOOL_APPROVAL_DECLINE: &str = "Deny"; const MCP_TOOL_APPROVAL_CANCEL: &str = "Cancel"; +#[derive(Debug, Serialize)] +struct McpToolApprovalKey { + server: String, + connector_id: String, + tool_name: String, +} + async fn maybe_request_mcp_tool_approval( sess: &Session, turn_context: &TurnContext, @@ -198,6 +210,19 @@ async fn maybe_request_mcp_tool_approval( if !requires_mcp_tool_approval(&metadata.annotations) { return None; } + let approval_key = metadata + .connector_id + .as_deref() + .map(|connector_id| McpToolApprovalKey { + server: server.to_string(), + connector_id: connector_id.to_string(), + tool_name: tool_name.to_string(), + }); + if let Some(key) = approval_key.as_ref() + && mcp_tool_approval_is_remembered(sess, key).await + { + return Some(McpToolApprovalDecision::Accept); + } let question_id = format!("{MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX}_{call_id}"); let question = build_mcp_tool_approval_question( @@ -206,6 +231,7 @@ async fn maybe_request_mcp_tool_approval( metadata.tool_title.as_deref(), metadata.connector_name.as_deref(), &metadata.annotations, + approval_key.is_some(), ); let args = RequestUserInputArgs { questions: vec![question], @@ -213,7 +239,13 @@ async fn maybe_request_mcp_tool_approval( let response = sess .request_user_input(turn_context, call_id.to_string(), args) .await; - Some(parse_mcp_tool_approval_response(response, &question_id)) + let decision = parse_mcp_tool_approval_response(response, &question_id); + if matches!(decision, McpToolApprovalDecision::AcceptAndRemember) + && let Some(key) = approval_key + { + remember_mcp_tool_approval(sess, key).await; + } + Some(decision) } fn is_full_access_mode(turn_context: &TurnContext) -> bool { @@ -244,6 +276,7 @@ async fn lookup_mcp_tool_metadata( .annotations .map(|annotations| McpToolApprovalMetadata { annotations, + connector_id: tool_info.connector_id, connector_name: tool_info.connector_name, tool_title: tool_info.tool.title, }) @@ -259,6 +292,7 @@ fn build_mcp_tool_approval_question( tool_title: Option<&str>, connector_name: Option<&str>, annotations: &ToolAnnotations, + allow_remember_option: bool, ) -> RequestUserInputQuestion { let destructive = annotations.destructive_hint == Some(true); let open_world = annotations.open_world_hint == Some(true); @@ -277,26 +311,34 @@ fn build_mcp_tool_approval_question( "{app_label} wants to run the tool \"{tool_label}\", which {reason}. Allow this action?" ); + let mut options = vec![RequestUserInputQuestionOption { + label: MCP_TOOL_APPROVAL_ACCEPT.to_string(), + description: "Run the tool and continue.".to_string(), + }]; + if allow_remember_option { + options.push(RequestUserInputQuestionOption { + label: MCP_TOOL_APPROVAL_ACCEPT_AND_REMEMBER.to_string(), + description: "Run the tool and remember this choice for this session.".to_string(), + }); + } + options.extend([ + RequestUserInputQuestionOption { + label: MCP_TOOL_APPROVAL_DECLINE.to_string(), + description: "Decline this tool call and continue.".to_string(), + }, + RequestUserInputQuestionOption { + label: MCP_TOOL_APPROVAL_CANCEL.to_string(), + description: "Cancel this tool call".to_string(), + }, + ]); + RequestUserInputQuestion { id: question_id, header: "Approve app tool call?".to_string(), question, is_other: false, is_secret: false, - options: Some(vec![ - RequestUserInputQuestionOption { - label: MCP_TOOL_APPROVAL_ACCEPT.to_string(), - description: "Run the tool and continue.".to_string(), - }, - RequestUserInputQuestionOption { - label: MCP_TOOL_APPROVAL_DECLINE.to_string(), - description: "Decline this tool call and continue.".to_string(), - }, - RequestUserInputQuestionOption { - label: MCP_TOOL_APPROVAL_CANCEL.to_string(), - description: "Cancel this tool call".to_string(), - }, - ]), + options: Some(options), } } @@ -315,6 +357,11 @@ fn parse_mcp_tool_approval_response( return McpToolApprovalDecision::Cancel; }; if answers + .iter() + .any(|answer| answer == MCP_TOOL_APPROVAL_ACCEPT_AND_REMEMBER) + { + McpToolApprovalDecision::AcceptAndRemember + } else if answers .iter() .any(|answer| answer == MCP_TOOL_APPROVAL_ACCEPT) { @@ -329,6 +376,16 @@ fn parse_mcp_tool_approval_response( } } +async fn mcp_tool_approval_is_remembered(sess: &Session, key: &McpToolApprovalKey) -> bool { + let store = sess.services.tool_approvals.lock().await; + matches!(store.get(key), Some(ReviewDecision::ApprovedForSession)) +} + +async fn remember_mcp_tool_approval(sess: &Session, key: McpToolApprovalKey) { + let mut store = sess.services.tool_approvals.lock().await; + store.put(key, ReviewDecision::ApprovedForSession); +} + fn requires_mcp_tool_approval(annotations: &ToolAnnotations) -> bool { annotations.read_only_hint == Some(false) && (annotations.destructive_hint == Some(true) || annotations.open_world_hint == Some(true)) From 71e63f8d10f789b697548bfaa1ef714f049fe226 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 17:59:22 +0000 Subject: [PATCH 23/90] fix: flaky test (#10644) --- codex-rs/core/tests/suite/tool_parallelism.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codex-rs/core/tests/suite/tool_parallelism.rs b/codex-rs/core/tests/suite/tool_parallelism.rs index 955b2f7ec3..0df8f8cbc9 100644 --- a/codex-rs/core/tests/suite/tool_parallelism.rs +++ b/codex-rs/core/tests/suite/tool_parallelism.rs @@ -149,6 +149,8 @@ async fn shell_tools_run_in_parallel() -> anyhow::Result<()> { let shell_args = json!({ "command": "sleep 0.3", + // Avoid user-specific shell startup cost (e.g. zsh profile scripts) in timing assertions. + "login": false, "timeout_ms": 1_000, }); let args_one = serde_json::to_string(&shell_args)?; From e9335374b9ef43710e76a411caca5e3a0bdf139b Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 17:59:36 +0000 Subject: [PATCH 24/90] feat: add phase 1 mem client (#10629) Adding a client on top of https://github.com/openai/openai/pull/672176 --- codex-rs/codex-api/README.md | 9 + codex-rs/codex-api/src/common.rs | 28 ++ codex-rs/codex-api/src/endpoint/memories.rs | 108 ++++++++ codex-rs/codex-api/src/endpoint/mod.rs | 1 + codex-rs/codex-api/src/lib.rs | 5 + codex-rs/core/src/client.rs | 58 +++- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/memory_trace.rs | 292 ++++++++++++++++++++ 8 files changed, 498 insertions(+), 4 deletions(-) create mode 100644 codex-rs/codex-api/src/endpoint/memories.rs create mode 100644 codex-rs/core/src/memory_trace.rs diff --git a/codex-rs/codex-api/README.md b/codex-rs/codex-api/README.md index c1f7d230c0..0570cf7570 100644 --- a/codex-rs/codex-api/README.md +++ b/codex-rs/codex-api/README.md @@ -29,4 +29,13 @@ The public interface of this crate is intentionally small and uniform: - Output: `Vec`. - `CompactClient::compact_input(&CompactionInput, extra_headers)` wraps the JSON encoding and retry/telemetry wiring. +- **Memory trace summarize endpoint** + - Input: `MemoryTraceSummarizeInput` (re-exported as `codex_api::MemoryTraceSummarizeInput`): + - `model: String`. + - `traces: Vec`. + - `MemoryTrace` includes `id`, `metadata.source_path`, and normalized `items`. + - `reasoning: Option`. + - Output: `Vec`. + - `MemoriesClient::trace_summarize_input(&MemoryTraceSummarizeInput, extra_headers)` wraps JSON encoding and retry/telemetry wiring. + All HTTP details (URLs, headers, retry/backoff policies, SSE framing) are encapsulated in `codex-api` and `codex-client`. Callers construct prompts/inputs using protocol types and work with typed streams of `ResponseEvent` or compacted `ResponseItem` values. diff --git a/codex-rs/codex-api/src/common.rs b/codex-rs/codex-api/src/common.rs index a9127644f1..f1a996f12c 100644 --- a/codex-rs/codex-api/src/common.rs +++ b/codex-rs/codex-api/src/common.rs @@ -6,6 +6,7 @@ use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use codex_protocol::protocol::RateLimitSnapshot; use codex_protocol::protocol::TokenUsage; use futures::Stream; +use serde::Deserialize; use serde::Serialize; use serde_json::Value; use std::pin::Pin; @@ -37,6 +38,33 @@ pub struct CompactionInput<'a> { pub instructions: &'a str, } +/// Canonical input payload for the memory trace summarize endpoint. +#[derive(Debug, Clone, Serialize)] +pub struct MemoryTraceSummarizeInput { + pub model: String, + pub traces: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct MemoryTrace { + pub id: String, + pub metadata: MemoryTraceMetadata, + pub items: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct MemoryTraceMetadata { + pub source_path: String, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct MemoryTraceSummaryOutput { + pub trace_summary: String, + pub memory_summary: String, +} + #[derive(Debug)] pub enum ResponseEvent { Created, diff --git a/codex-rs/codex-api/src/endpoint/memories.rs b/codex-rs/codex-api/src/endpoint/memories.rs new file mode 100644 index 0000000000..c8f35d7e16 --- /dev/null +++ b/codex-rs/codex-api/src/endpoint/memories.rs @@ -0,0 +1,108 @@ +use crate::auth::AuthProvider; +use crate::common::MemoryTraceSummarizeInput; +use crate::common::MemoryTraceSummaryOutput; +use crate::endpoint::session::EndpointSession; +use crate::error::ApiError; +use crate::provider::Provider; +use codex_client::HttpTransport; +use codex_client::RequestTelemetry; +use http::HeaderMap; +use http::Method; +use serde::Deserialize; +use serde_json::to_value; +use std::sync::Arc; + +pub struct MemoriesClient { + session: EndpointSession, +} + +impl MemoriesClient { + pub fn new(transport: T, provider: Provider, auth: A) -> Self { + Self { + session: EndpointSession::new(transport, provider, auth), + } + } + + pub fn with_telemetry(self, request: Option>) -> Self { + Self { + session: self.session.with_request_telemetry(request), + } + } + + fn path() -> &'static str { + "memories/trace_summarize" + } + + pub async fn trace_summarize( + &self, + body: serde_json::Value, + extra_headers: HeaderMap, + ) -> Result, ApiError> { + let resp = self + .session + .execute(Method::POST, Self::path(), extra_headers, Some(body)) + .await?; + let parsed: TraceSummarizeResponse = + serde_json::from_slice(&resp.body).map_err(|e| ApiError::Stream(e.to_string()))?; + Ok(parsed.output) + } + + pub async fn trace_summarize_input( + &self, + input: &MemoryTraceSummarizeInput, + extra_headers: HeaderMap, + ) -> Result, ApiError> { + let body = to_value(input).map_err(|e| { + ApiError::Stream(format!( + "failed to encode memory trace summarize input: {e}" + )) + })?; + self.trace_summarize(body, extra_headers).await + } +} + +#[derive(Debug, Deserialize)] +struct TraceSummarizeResponse { + output: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use async_trait::async_trait; + use codex_client::Request; + use codex_client::Response; + use codex_client::StreamResponse; + use codex_client::TransportError; + + #[derive(Clone, Default)] + struct DummyTransport; + + #[async_trait] + impl HttpTransport for DummyTransport { + async fn execute(&self, _req: Request) -> Result { + Err(TransportError::Build("execute should not run".to_string())) + } + + async fn stream(&self, _req: Request) -> Result { + Err(TransportError::Build("stream should not run".to_string())) + } + } + + #[derive(Clone, Default)] + struct DummyAuth; + + impl AuthProvider for DummyAuth { + fn bearer_token(&self) -> Option { + None + } + } + + #[test] + fn path_is_memories_trace_summarize() { + assert_eq!( + MemoriesClient::::path(), + "memories/trace_summarize" + ); + } +} diff --git a/codex-rs/codex-api/src/endpoint/mod.rs b/codex-rs/codex-api/src/endpoint/mod.rs index 23579ffcf1..0dede138e8 100644 --- a/codex-rs/codex-api/src/endpoint/mod.rs +++ b/codex-rs/codex-api/src/endpoint/mod.rs @@ -1,5 +1,6 @@ pub mod aggregate; pub mod compact; +pub mod memories; pub mod models; pub mod responses; pub mod responses_websocket; diff --git a/codex-rs/codex-api/src/lib.rs b/codex-rs/codex-api/src/lib.rs index b0c70084d4..70652d2d78 100644 --- a/codex-rs/codex-api/src/lib.rs +++ b/codex-rs/codex-api/src/lib.rs @@ -15,6 +15,10 @@ pub use codex_client::TransportError; pub use crate::auth::AuthProvider; pub use crate::common::CompactionInput; +pub use crate::common::MemoryTrace; +pub use crate::common::MemoryTraceMetadata; +pub use crate::common::MemoryTraceSummarizeInput; +pub use crate::common::MemoryTraceSummaryOutput; pub use crate::common::Prompt; pub use crate::common::ResponseAppendWsRequest; pub use crate::common::ResponseCreateWsRequest; @@ -24,6 +28,7 @@ pub use crate::common::ResponsesApiRequest; pub use crate::common::create_text_param_for_request; pub use crate::endpoint::aggregate::AggregateStreamExt; pub use crate::endpoint::compact::CompactClient; +pub use crate::endpoint::memories::MemoriesClient; pub use crate::endpoint::models::ModelsClient; pub use crate::endpoint::responses::ResponsesClient; pub use crate::endpoint::responses::ResponsesOptions; diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index dd92c5da01..0a0f702b17 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -7,6 +7,10 @@ use crate::api_bridge::map_api_error; use crate::auth::UnauthorizedRecovery; use codex_api::CompactClient as ApiCompactClient; use codex_api::CompactionInput as ApiCompactionInput; +use codex_api::MemoriesClient as ApiMemoriesClient; +use codex_api::MemoryTrace as ApiMemoryTrace; +use codex_api::MemoryTraceSummarizeInput as ApiMemoryTraceSummarizeInput; +use codex_api::MemoryTraceSummaryOutput as ApiMemoryTraceSummaryOutput; use codex_api::Prompt as ApiPrompt; use codex_api::RequestTelemetry; use codex_api::ReqwestTransport; @@ -183,6 +187,55 @@ impl ModelClient { instructions: &instructions, }; + let extra_headers = self.build_subagent_headers(); + client + .compact_input(&payload, extra_headers) + .await + .map_err(map_api_error) + } + + /// Builds memory summaries for each provided normalized trace. + /// + /// This is a unary call (no streaming) to `/v1/memories/trace_summarize`. + pub async fn summarize_memory_traces( + &self, + traces: Vec, + ) -> Result> { + if traces.is_empty() { + return Ok(Vec::new()); + } + + let auth_manager = self.state.auth_manager.clone(); + let auth = match auth_manager.as_ref() { + Some(manager) => manager.auth().await, + None => None, + }; + let api_provider = self + .state + .provider + .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; + let api_auth = auth_provider_from_auth(auth, &self.state.provider)?; + let transport = ReqwestTransport::new(build_reqwest_client()); + let request_telemetry = self.build_request_telemetry(); + let client = ApiMemoriesClient::new(transport, api_provider, api_auth) + .with_telemetry(Some(request_telemetry)); + + let payload = ApiMemoryTraceSummarizeInput { + model: self.state.model_info.slug.clone(), + traces, + reasoning: self.state.effort.map(|effort| Reasoning { + effort: Some(effort), + summary: None, + }), + }; + + client + .trace_summarize_input(&payload, self.build_subagent_headers()) + .await + .map_err(map_api_error) + } + + fn build_subagent_headers(&self) -> ApiHeaderMap { let mut extra_headers = ApiHeaderMap::new(); if let SessionSource::SubAgent(sub) = &self.state.session_source { let subagent = match sub { @@ -195,10 +248,7 @@ impl ModelClient { extra_headers.insert("x-openai-subagent", val); } } - client - .compact_input(&payload, extra_headers) - .await - .map_err(map_api_error) + extra_headers } } diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 9bd4c87253..36fdbdc9a6 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -161,4 +161,5 @@ pub use codex_protocol::models::ResponseItem; pub use compact::content_items_to_text; pub use event_mapping::parse_turn_item; pub mod compact; +pub mod memory_trace; pub mod otel_init; diff --git a/codex-rs/core/src/memory_trace.rs b/codex-rs/core/src/memory_trace.rs new file mode 100644 index 0000000000..807ff64df1 --- /dev/null +++ b/codex-rs/core/src/memory_trace.rs @@ -0,0 +1,292 @@ +use std::path::Path; +use std::path::PathBuf; + +use crate::ModelClient; +use crate::error::CodexErr; +use crate::error::Result; +use codex_api::MemoryTrace as ApiMemoryTrace; +use codex_api::MemoryTraceMetadata as ApiMemoryTraceMetadata; +use serde_json::Map; +use serde_json::Value; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BuiltTraceMemory { + pub trace_id: String, + pub source_path: PathBuf, + pub trace_summary: String, + pub memory_summary: String, +} + +struct PreparedTrace { + trace_id: String, + source_path: PathBuf, + payload: ApiMemoryTrace, +} + +/// Loads raw trace files, normalizes trace items, and builds memory summaries. +/// +/// The request/response wiring mirrors the memory trace summarize E2E flow: +/// `/v1/memories/trace_summarize` with one output object per input trace. +pub async fn build_memories_from_trace_files( + client: &ModelClient, + trace_paths: &[PathBuf], +) -> Result> { + if trace_paths.is_empty() { + return Ok(Vec::new()); + } + + let mut prepared = Vec::with_capacity(trace_paths.len()); + for (index, path) in trace_paths.iter().enumerate() { + prepared.push(prepare_trace(index + 1, path).await?); + } + + let traces = prepared.iter().map(|trace| trace.payload.clone()).collect(); + let output = client.summarize_memory_traces(traces).await?; + if output.len() != prepared.len() { + return Err(CodexErr::InvalidRequest(format!( + "unexpected memory summarize output length: expected {}, got {}", + prepared.len(), + output.len() + ))); + } + + Ok(prepared + .into_iter() + .zip(output) + .map(|(trace, summary)| BuiltTraceMemory { + trace_id: trace.trace_id, + source_path: trace.source_path, + trace_summary: summary.trace_summary, + memory_summary: summary.memory_summary, + }) + .collect()) +} + +async fn prepare_trace(index: usize, path: &Path) -> Result { + let text = load_trace_text(path).await?; + let items = load_trace_items(path, &text)?; + let trace_id = build_trace_id(index, path); + let source_path = path.to_path_buf(); + + Ok(PreparedTrace { + trace_id: trace_id.clone(), + source_path: source_path.clone(), + payload: ApiMemoryTrace { + id: trace_id, + metadata: ApiMemoryTraceMetadata { + source_path: source_path.display().to_string(), + }, + items, + }, + }) +} + +async fn load_trace_text(path: &Path) -> Result { + let raw = tokio::fs::read(path).await?; + Ok(decode_trace_bytes(&raw)) +} + +fn decode_trace_bytes(raw: &[u8]) -> String { + if let Some(without_bom) = raw.strip_prefix(&[0xEF, 0xBB, 0xBF]) + && let Ok(text) = String::from_utf8(without_bom.to_vec()) + { + return text; + } + if let Ok(text) = String::from_utf8(raw.to_vec()) { + return text; + } + raw.iter().map(|b| char::from(*b)).collect() +} + +fn load_trace_items(path: &Path, text: &str) -> Result> { + if let Ok(Value::Array(items)) = serde_json::from_str::(text) { + let dict_items = items + .into_iter() + .filter(serde_json::Value::is_object) + .collect::>(); + if dict_items.is_empty() { + return Err(CodexErr::InvalidRequest(format!( + "no object items found in trace file: {}", + path.display() + ))); + } + return normalize_trace_items(dict_items, path); + } + + let mut parsed_items = Vec::new(); + for line in text.lines() { + let line = line.trim(); + if line.is_empty() || (!line.starts_with('{') && !line.starts_with('[')) { + continue; + } + + let Ok(obj) = serde_json::from_str::(line) else { + continue; + }; + + match obj { + Value::Object(_) => parsed_items.push(obj), + Value::Array(inner) => { + parsed_items.extend(inner.into_iter().filter(serde_json::Value::is_object)) + } + _ => {} + } + } + + if parsed_items.is_empty() { + return Err(CodexErr::InvalidRequest(format!( + "no JSON items parsed from trace file: {}", + path.display() + ))); + } + + normalize_trace_items(parsed_items, path) +} + +fn normalize_trace_items(items: Vec, path: &Path) -> Result> { + let mut normalized = Vec::new(); + + for item in items { + let Value::Object(obj) = item else { + continue; + }; + + if let Some(payload) = obj.get("payload") { + if obj.get("type").and_then(Value::as_str) != Some("response_item") { + continue; + } + + match payload { + Value::Object(payload_item) => { + if is_allowed_trace_item(payload_item) { + normalized.push(Value::Object(payload_item.clone())); + } + } + Value::Array(payload_items) => { + for payload_item in payload_items { + if let Value::Object(payload_item) = payload_item + && is_allowed_trace_item(payload_item) + { + normalized.push(Value::Object(payload_item.clone())); + } + } + } + _ => {} + } + continue; + } + + if is_allowed_trace_item(&obj) { + normalized.push(Value::Object(obj)); + } + } + + if normalized.is_empty() { + return Err(CodexErr::InvalidRequest(format!( + "no valid trace items after normalization: {}", + path.display() + ))); + } + Ok(normalized) +} + +fn is_allowed_trace_item(item: &Map) -> bool { + let Some(item_type) = item.get("type").and_then(Value::as_str) else { + return false; + }; + + if item_type == "message" { + return matches!( + item.get("role").and_then(Value::as_str), + Some("assistant" | "system" | "developer" | "user") + ); + } + + true +} + +fn build_trace_id(index: usize, path: &Path) -> String { + let stem = path + .file_stem() + .map(|stem| stem.to_string_lossy().into_owned()) + .filter(|stem| !stem.is_empty()) + .unwrap_or_else(|| "trace".to_string()); + format!("trace_{index}_{stem}") +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use tempfile::tempdir; + + #[test] + fn normalize_trace_items_handles_payload_wrapper_and_message_role_filtering() { + let items = vec![ + serde_json::json!({ + "type": "response_item", + "payload": {"type": "message", "role": "assistant", "content": []} + }), + serde_json::json!({ + "type": "response_item", + "payload": [ + {"type": "message", "role": "user", "content": []}, + {"type": "message", "role": "tool", "content": []}, + {"type": "function_call", "name": "shell", "arguments": "{}", "call_id": "c1"} + ] + }), + serde_json::json!({ + "type": "not_response_item", + "payload": {"type": "message", "role": "assistant", "content": []} + }), + serde_json::json!({ + "type": "message", + "role": "developer", + "content": [] + }), + ]; + + let normalized = normalize_trace_items(items, Path::new("trace.json")).expect("normalize"); + let expected = vec![ + serde_json::json!({"type": "message", "role": "assistant", "content": []}), + serde_json::json!({"type": "message", "role": "user", "content": []}), + serde_json::json!({"type": "function_call", "name": "shell", "arguments": "{}", "call_id": "c1"}), + serde_json::json!({"type": "message", "role": "developer", "content": []}), + ]; + assert_eq!(normalized, expected); + } + + #[test] + fn load_trace_items_supports_jsonl_arrays_and_objects() { + let text = r#" +{"type":"response_item","payload":{"type":"message","role":"assistant","content":[]}} +[{"type":"message","role":"user","content":[]},{"type":"message","role":"tool","content":[]}] +"#; + let loaded = load_trace_items(Path::new("trace.jsonl"), text).expect("load"); + let expected = vec![ + serde_json::json!({"type":"message","role":"assistant","content":[]}), + serde_json::json!({"type":"message","role":"user","content":[]}), + ]; + assert_eq!(loaded, expected); + } + + #[tokio::test] + async fn load_trace_text_decodes_utf8_sig() { + let dir = tempdir().expect("tempdir"); + let path = dir.path().join("trace.json"); + tokio::fs::write( + &path, + [ + 0xEF, 0xBB, 0xBF, b'[', b'{', b'"', b't', b'y', b'p', b'e', b'"', b':', b'"', b'm', + b'e', b's', b's', b'a', b'g', b'e', b'"', b',', b'"', b'r', b'o', b'l', b'e', b'"', + b':', b'"', b'u', b's', b'e', b'r', b'"', b',', b'"', b'c', b'o', b'n', b't', b'e', + b'n', b't', b'"', b':', b'[', b']', b'}', b']', + ], + ) + .await + .expect("write"); + + let text = load_trace_text(&path).await.expect("decode"); + assert!(text.starts_with('[')); + } +} From 1b153a3d4a53a28bd0fe612c1136cfe96f7ef038 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Wed, 4 Feb 2026 18:40:56 +0000 Subject: [PATCH 25/90] Cloud Requirements: take precedence over MDM (#10633) Cloud Requirements should be applied before MDM requirements. --- codex-rs/core/src/config_loader/mod.rs | 12 +++--- codex-rs/core/src/config_loader/tests.rs | 53 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index e9cd1c68e8..28fda87357 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -72,8 +72,8 @@ const DEFAULT_PROJECT_ROOT_MARKERS: &[&str] = &[".git"]; /// configuration layers in the following order, but a constraint defined in an /// earlier layer cannot be overridden by a later layer: /// -/// - admin: managed preferences (*) /// - cloud: managed cloud requirements +/// - admin: managed preferences (*) /// - system `/etc/codex/requirements.toml` /// /// For backwards compatibility, we also load from @@ -107,6 +107,11 @@ pub async fn load_config_layers_state( ) -> io::Result { let mut config_requirements_toml = ConfigRequirementsWithSources::default(); + if let Some(requirements) = cloud_requirements.get().await { + config_requirements_toml + .merge_unset_fields(RequirementSource::CloudRequirements, requirements); + } + #[cfg(target_os = "macos")] macos::load_managed_admin_requirements_toml( &mut config_requirements_toml, @@ -116,11 +121,6 @@ pub async fn load_config_layers_state( ) .await?; - if let Some(requirements) = cloud_requirements.get().await { - config_requirements_toml - .merge_unset_fields(RequirementSource::CloudRequirements, requirements); - } - // Honor /etc/codex/requirements.toml. if cfg!(unix) { load_requirements_toml( diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index cbd49f131c..2d7e36d6ef 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -487,6 +487,59 @@ enforce_residency = "us" Ok(()) } +#[cfg(target_os = "macos")] +#[tokio::test] +async fn cloud_requirements_take_precedence_over_mdm_requirements() -> anyhow::Result<()> { + use base64::Engine; + + let tmp = tempdir()?; + let state = load_config_layers_state( + tmp.path(), + Some(AbsolutePathBuf::try_from(tmp.path())?), + &[] as &[(String, TomlValue)], + LoaderOverrides { + macos_managed_config_requirements_base64: Some( + base64::prelude::BASE64_STANDARD.encode( + r#" +allowed_approval_policies = ["on-request"] +"# + .as_bytes(), + ), + ), + ..LoaderOverrides::default() + }, + CloudRequirementsLoader::new(async { + Some(ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::Never]), + allowed_sandbox_modes: None, + mcp_servers: None, + rules: None, + enforce_residency: None, + }) + }), + ) + .await?; + + assert_eq!( + state.requirements().approval_policy.value(), + AskForApproval::Never + ); + assert_eq!( + state + .requirements() + .approval_policy + .can_set(&AskForApproval::OnRequest), + Err(ConstraintError::InvalidValue { + field_name: "approval_policy", + candidate: "OnRequest".into(), + allowed: "[Never]".into(), + requirement_source: RequirementSource::CloudRequirements, + }) + ); + + Ok(()) +} + #[tokio::test(flavor = "current_thread")] async fn cloud_requirements_are_not_overwritten_by_system_requirements() -> anyhow::Result<()> { let tmp = tempdir()?; From 95269ce88b894bf8480ea9b21bb56d040cd40483 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Wed, 4 Feb 2026 18:57:39 +0000 Subject: [PATCH 26/90] Increase cloud req timeout (#10659) 5s -> 15s --- codex-rs/cloud-requirements/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 535fef3876..95f5180a83 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -20,8 +20,7 @@ use std::time::Duration; use std::time::Instant; use tokio::time::timeout; -/// This blocks codex startup, so must be short. -const CLOUD_REQUIREMENTS_TIMEOUT: Duration = Duration::from_secs(5); +const CLOUD_REQUIREMENTS_TIMEOUT: Duration = Duration::from_secs(15); #[async_trait] trait RequirementsFetcher: Send + Sync { From ae4de43ccc894e389868b60e190306301a37806b Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Wed, 4 Feb 2026 11:13:17 -0800 Subject: [PATCH 27/90] feat(linux-sandbox): add bwrap support (#9938) ## Summary This PR introduces a gated Bubblewrap (bwrap) Linux sandbox path. The curent Linux sandbox path relies on in-process restrictions (including Landlock). Bubblewrap gives us a more uniform filesystem isolation model, especially explicit writable roots with the option to make some directories read-only and granular network controls. This is behind a feature flag so we can validate behavior safely before making it the default. - Added temporary rollout flag: - `features.use_linux_sandbox_bwrap` - Preserved existing default path when the flag is off. - In Bubblewrap mode: - Added internal retry without /proc when /proc mount is not permitted by the host/container. --- .github/workflows/bazel.yml | 1 + .github/workflows/rust-ci.yml | 7 +- .github/workflows/rust-release.yml | 9 + codex-rs/Cargo.lock | 1 - .../app-server/src/codex_message_processor.rs | 2 + codex-rs/cli/src/debug_sandbox.rs | 3 + codex-rs/common/src/config_override.rs | 20 +- codex-rs/core/config.schema.json | 6 + codex-rs/core/src/codex.rs | 8 + codex-rs/core/src/connectors.rs | 1 + codex-rs/core/src/exec.rs | 15 +- codex-rs/core/src/features.rs | 8 + codex-rs/core/src/landlock.rs | 53 ++- codex-rs/core/src/mcp/mod.rs | 1 + codex-rs/core/src/mcp_connection_manager.rs | 2 + codex-rs/core/src/sandboxing/mod.rs | 37 +- codex-rs/core/src/tools/orchestrator.rs | 4 + codex-rs/core/src/tools/sandboxing.rs | 19 +- codex-rs/core/tests/suite/exec.rs | 2 +- .../exec-server/src/posix/escalate_server.rs | 1 + codex-rs/exec-server/src/posix/mcp.rs | 1 + codex-rs/exec-server/tests/common/lib.rs | 2 + codex-rs/exec/tests/suite/sandbox.rs | 1 + codex-rs/linux-sandbox/Cargo.toml | 1 - codex-rs/linux-sandbox/README.md | 25 ++ codex-rs/linux-sandbox/src/bwrap.rs | 39 -- codex-rs/linux-sandbox/src/landlock.rs | 29 +- codex-rs/linux-sandbox/src/linux_run_main.rs | 315 +++++++++++----- codex-rs/linux-sandbox/src/mounts.rs | 339 ------------------ codex-rs/linux-sandbox/src/vendored_bwrap.rs | 30 +- .../linux-sandbox/tests/suite/landlock.rs | 142 +++++++- 31 files changed, 607 insertions(+), 517 deletions(-) delete mode 100644 codex-rs/linux-sandbox/src/mounts.rs diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 55e05251d7..b5e91d7f22 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -100,6 +100,7 @@ jobs: - name: bazel test //... env: BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }} + CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }} shell: bash run: | bazel $BAZEL_STARTUP_ARGS --bazelrc=.github/workflows/ci.bazelrc test //... \ diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index ec6319f8da..b6a3c50e18 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -99,6 +99,9 @@ jobs: USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }} CARGO_INCREMENTAL: "0" SCCACHE_CACHE_SIZE: 10G + # Keep cargo-based CI independent of system bwrap build deps. + # The bwrap FFI path is validated in Bazel workflows. + CODEX_BWRAP_ENABLE_FFI: "0" strategy: fail-fast: false @@ -467,6 +470,9 @@ jobs: USE_SCCACHE: ${{ startsWith(matrix.runner, 'windows') && 'false' || 'true' }} CARGO_INCREMENTAL: "0" SCCACHE_CACHE_SIZE: 10G + # Keep cargo-based CI independent of system bwrap build deps. + # The bwrap FFI path is validated in Bazel workflows. + CODEX_BWRAP_ENABLE_FFI: "0" strategy: fail-fast: false @@ -502,7 +508,6 @@ jobs: steps: - uses: actions/checkout@v6 - # Some integration tests rely on DotSlash being installed. # See https://github.com/openai/codex/pull/7617. - name: Install DotSlash diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 2f764df680..aa92693eee 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -65,6 +65,8 @@ jobs: defaults: run: working-directory: codex-rs + env: + CODEX_BWRAP_ENABLE_FFI: ${{ contains(matrix.target, 'unknown-linux') && '1' || '0' }} strategy: fail-fast: false @@ -89,6 +91,13 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Install Linux bwrap build dependencies + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + set -euo pipefail + sudo apt-get update -y + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev - name: Install UBSan runtime (musl) if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl' }} shell: bash diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 43b4b295a8..d83626c4b7 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1797,7 +1797,6 @@ dependencies = [ "serde_json", "tempfile", "tokio", - "which", ] [[package]] diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 219cb46570..ef7e175b10 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1484,6 +1484,7 @@ impl CodexMessageProcessor { let outgoing = self.outgoing.clone(); let req_id = request_id; let sandbox_cwd = self.config.cwd.clone(); + let use_linux_sandbox_bwrap = self.config.features.enabled(Feature::UseLinuxSandboxBwrap); tokio::spawn(async move { match codex_core::exec::process_exec_tool_call( @@ -1491,6 +1492,7 @@ impl CodexMessageProcessor { &effective_policy, sandbox_cwd.as_path(), &codex_linux_sandbox_exe, + use_linux_sandbox_bwrap, None, ) .await diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 5b165f9778..30352cbb83 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -227,16 +227,19 @@ async fn run_command_under_sandbox( .await? } SandboxType::Landlock => { + use codex_core::features::Feature; #[expect(clippy::expect_used)] let codex_linux_sandbox_exe = config .codex_linux_sandbox_exe .expect("codex-linux-sandbox executable not found"); + let use_bwrap_sandbox = config.features.enabled(Feature::UseLinuxSandboxBwrap); spawn_command_under_linux_sandbox( codex_linux_sandbox_exe, command, cwd, config.sandbox_policy.get(), sandbox_policy_cwd.as_path(), + use_bwrap_sandbox, stdio_policy, env, ) diff --git a/codex-rs/common/src/config_override.rs b/codex-rs/common/src/config_override.rs index 59dde92a22..9bbec2b6f2 100644 --- a/codex-rs/common/src/config_override.rs +++ b/codex-rs/common/src/config_override.rs @@ -71,7 +71,7 @@ impl CliConfigOverrides { } }; - Ok((key.to_string(), value)) + Ok((canonicalize_override_key(key), value)) }) .collect() } @@ -88,6 +88,14 @@ impl CliConfigOverrides { } } +fn canonicalize_override_key(key: &str) -> String { + if key == "use_linux_sandbox_bwrap" { + "features.use_linux_sandbox_bwrap".to_string() + } else { + key.to_string() + } +} + /// Apply a single override onto `root`, creating intermediate objects as /// necessary. fn apply_single_override(root: &mut Value, path: &str, value: Value) { @@ -172,6 +180,16 @@ mod tests { assert_eq!(arr.len(), 3); } + #[test] + fn canonicalizes_use_linux_sandbox_bwrap_alias() { + let overrides = CliConfigOverrides { + raw_overrides: vec!["use_linux_sandbox_bwrap=true".to_string()], + }; + let parsed = overrides.parse_overrides().expect("parse_overrides"); + assert_eq!(parsed[0].0.as_str(), "features.use_linux_sandbox_bwrap"); + assert_eq!(parsed[0].1.as_bool(), Some(true)); + } + #[test] fn parses_inline_table() { let v = parse_toml_value("{a = 1, b = 2}").expect("parse"); diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index bddc9d8a0b..138b71e2dc 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -235,6 +235,9 @@ "unified_exec": { "type": "boolean" }, + "use_linux_sandbox_bwrap": { + "type": "boolean" + }, "web_search": { "type": "boolean" }, @@ -1253,6 +1256,9 @@ "unified_exec": { "type": "boolean" }, + "use_linux_sandbox_bwrap": { + "type": "boolean" + }, "web_search": { "type": "boolean" }, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 979f2a5f62..5368b8b062 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -508,6 +508,7 @@ pub(crate) struct TurnContext { pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) shell_environment_policy: ShellEnvironmentPolicy, pub(crate) tools_config: ToolsConfig, + pub(crate) features: Features, pub(crate) ghost_snapshot: GhostSnapshotConfig, pub(crate) final_output_json_schema: Option, pub(crate) codex_linux_sandbox_exe: Option, @@ -766,6 +767,7 @@ impl Session { windows_sandbox_level: session_configuration.windows_sandbox_level, shell_environment_policy: per_turn_config.shell_environment_policy.clone(), tools_config, + features: per_turn_config.features.clone(), ghost_snapshot: per_turn_config.ghost_snapshot.clone(), final_output_json_schema: None, codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), @@ -1036,6 +1038,7 @@ impl Session { sandbox_policy: session_configuration.sandbox_policy.get().clone(), codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), sandbox_cwd: session_configuration.cwd.clone(), + use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap), }; let cancel_token = sess.mcp_startup_cancellation_token().await; @@ -1285,6 +1288,9 @@ impl Session { sandbox_policy: per_turn_config.sandbox_policy.get().clone(), codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(), sandbox_cwd: per_turn_config.cwd.clone(), + use_linux_sandbox_bwrap: per_turn_config + .features + .enabled(Feature::UseLinuxSandboxBwrap), }; if let Err(e) = self .services @@ -2367,6 +2373,7 @@ impl Session { sandbox_policy: turn_context.sandbox_policy.clone(), codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(), sandbox_cwd: turn_context.cwd.clone(), + use_linux_sandbox_bwrap: turn_context.features.enabled(Feature::UseLinuxSandboxBwrap), }; let cancel_token = self.reset_mcp_startup_cancellation_token().await; @@ -3343,6 +3350,7 @@ async fn spawn_review_thread( session_source, transport_manager, tools_config, + features: parent_turn_context.features.clone(), ghost_snapshot: parent_turn_context.ghost_snapshot.clone(), developer_instructions: None, user_instructions: None, diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index edf3c63aa5..afe03fbbe7 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -43,6 +43,7 @@ pub async fn list_accessible_connectors_from_mcp_tools( sandbox_policy: SandboxPolicy::ReadOnly, codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), + use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap), }; mcp_connection_manager diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index e0d65c8305..3ac8d1956f 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -140,6 +140,7 @@ pub async fn process_exec_tool_call( sandbox_policy: &SandboxPolicy, sandbox_cwd: &Path, codex_linux_sandbox_exe: &Option, + use_linux_sandbox_bwrap: bool, stdout_stream: Option, ) -> Result { let windows_sandbox_level = params.windows_sandbox_level; @@ -184,14 +185,15 @@ pub async fn process_exec_tool_call( let manager = SandboxManager::new(); let exec_env = manager - .transform( + .transform(crate::sandboxing::SandboxTransformRequest { spec, - sandbox_policy, - sandbox_type, - sandbox_cwd, - codex_linux_sandbox_exe.as_ref(), + policy: sandbox_policy, + sandbox: sandbox_type, + sandbox_policy_cwd: sandbox_cwd, + codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(), + use_linux_sandbox_bwrap, windows_sandbox_level, - ) + }) .map_err(CodexErr::from)?; // Route through the sandboxing module for a single, unified execution path. @@ -1108,6 +1110,7 @@ mod tests { &SandboxPolicy::DangerFullAccess, cwd.as_path(), &None, + false, None, ) .await; diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index a44477905e..f6c286c571 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -89,6 +89,8 @@ pub enum Feature { WebSearchCached, /// Gate the execpolicy enforcement for shell/unified exec. ExecPolicy, + /// Use the bubblewrap-based Linux sandbox pipeline. + UseLinuxSandboxBwrap, /// Allow the model to request approval and propose exec rules. RequestRule, /// Enable Windows sandbox (restricted token) on Windows. @@ -465,6 +467,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::UnderDevelopment, default_enabled: true, }, + FeatureSpec { + id: Feature::UseLinuxSandboxBwrap, + key: "use_linux_sandbox_bwrap", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::RequestRule, key: "request_rule", diff --git a/codex-rs/core/src/landlock.rs b/codex-rs/core/src/landlock.rs index 340aebff2c..ea27f77f75 100644 --- a/codex-rs/core/src/landlock.rs +++ b/codex-rs/core/src/landlock.rs @@ -6,26 +6,34 @@ use std::path::Path; use std::path::PathBuf; use tokio::process::Child; -/// Spawn a shell tool command under the Linux Landlock+seccomp sandbox helper -/// (codex-linux-sandbox). +/// Spawn a shell tool command under the Linux sandbox helper +/// (codex-linux-sandbox), which currently uses bubblewrap for filesystem +/// isolation plus seccomp for network restrictions. /// /// Unlike macOS Seatbelt where we directly embed the policy text, the Linux /// helper accepts a list of `--sandbox-permission`/`-s` flags mirroring the /// public CLI. We convert the internal [`SandboxPolicy`] representation into /// the equivalent CLI options. +#[allow(clippy::too_many_arguments)] pub async fn spawn_command_under_linux_sandbox

( codex_linux_sandbox_exe: P, command: Vec, command_cwd: PathBuf, sandbox_policy: &SandboxPolicy, sandbox_policy_cwd: &Path, + use_bwrap_sandbox: bool, stdio_policy: StdioPolicy, env: HashMap, ) -> std::io::Result where P: AsRef, { - let args = create_linux_sandbox_command_args(command, sandbox_policy, sandbox_policy_cwd); + let args = create_linux_sandbox_command_args( + command, + sandbox_policy, + sandbox_policy_cwd, + use_bwrap_sandbox, + ); let arg0 = Some("codex-linux-sandbox"); spawn_child_async( codex_linux_sandbox_exe.as_ref().to_path_buf(), @@ -40,10 +48,14 @@ where } /// Converts the sandbox policy into the CLI invocation for `codex-linux-sandbox`. +/// +/// The helper performs the actual sandboxing (bubblewrap + seccomp) after +/// parsing these arguments. See `docs/linux_sandbox.md` for the Linux semantics. pub(crate) fn create_linux_sandbox_command_args( command: Vec, sandbox_policy: &SandboxPolicy, sandbox_policy_cwd: &Path, + use_bwrap_sandbox: bool, ) -> Vec { #[expect(clippy::expect_used)] let sandbox_policy_cwd = sandbox_policy_cwd @@ -60,13 +72,42 @@ pub(crate) fn create_linux_sandbox_command_args( sandbox_policy_cwd, "--sandbox-policy".to_string(), sandbox_policy_json, - // Separator so that command arguments starting with `-` are not parsed as - // options of the helper itself. - "--".to_string(), ]; + if use_bwrap_sandbox { + linux_cmd.push("--use-bwrap-sandbox".to_string()); + } + + // Separator so that command arguments starting with `-` are not parsed as + // options of the helper itself. + linux_cmd.push("--".to_string()); // Append the original tool command. linux_cmd.extend(command); linux_cmd } + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn bwrap_flags_are_feature_gated() { + let command = vec!["/bin/true".to_string()]; + let cwd = Path::new("/tmp"); + let policy = SandboxPolicy::ReadOnly; + + let with_bwrap = create_linux_sandbox_command_args(command.clone(), &policy, cwd, true); + assert_eq!( + with_bwrap.contains(&"--use-bwrap-sandbox".to_string()), + true + ); + + let without_bwrap = create_linux_sandbox_command_args(command, &policy, cwd, false); + assert_eq!( + without_bwrap.contains(&"--use-bwrap-sandbox".to_string()), + false + ); + } +} diff --git a/codex-rs/core/src/mcp/mod.rs b/codex-rs/core/src/mcp/mod.rs index cb0b5e2f26..970fa5db40 100644 --- a/codex-rs/core/src/mcp/mod.rs +++ b/codex-rs/core/src/mcp/mod.rs @@ -167,6 +167,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent sandbox_policy: SandboxPolicy::ReadOnly, codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(), sandbox_cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), + use_linux_sandbox_bwrap: config.features.enabled(Feature::UseLinuxSandboxBwrap), }; mcp_connection_manager diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index c5fbb8ec3d..456114c30f 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -313,6 +313,8 @@ pub struct SandboxState { pub sandbox_policy: SandboxPolicy, pub codex_linux_sandbox_exe: Option, pub sandbox_cwd: PathBuf, + #[serde(default)] + pub use_linux_sandbox_bwrap: bool, } /// A thin wrapper around a set of running [`RmcpClient`] instances. diff --git a/codex-rs/core/src/sandboxing/mod.rs b/codex-rs/core/src/sandboxing/mod.rs index fca7adda29..bc914abe5b 100644 --- a/codex-rs/core/src/sandboxing/mod.rs +++ b/codex-rs/core/src/sandboxing/mod.rs @@ -51,6 +51,19 @@ pub struct ExecEnv { pub arg0: Option, } +/// Bundled arguments for sandbox transformation. +/// +/// This keeps call sites self-documenting when several fields are optional. +pub(crate) struct SandboxTransformRequest<'a> { + pub spec: CommandSpec, + pub policy: &'a SandboxPolicy, + pub sandbox: SandboxType, + pub sandbox_policy_cwd: &'a Path, + pub codex_linux_sandbox_exe: Option<&'a PathBuf>, + pub use_linux_sandbox_bwrap: bool, + pub windows_sandbox_level: WindowsSandboxLevel, +} + pub enum SandboxPreference { Auto, Require, @@ -104,13 +117,17 @@ impl SandboxManager { pub(crate) fn transform( &self, - mut spec: CommandSpec, - policy: &SandboxPolicy, - sandbox: SandboxType, - sandbox_policy_cwd: &Path, - codex_linux_sandbox_exe: Option<&PathBuf>, - windows_sandbox_level: WindowsSandboxLevel, + request: SandboxTransformRequest<'_>, ) -> Result { + let SandboxTransformRequest { + mut spec, + policy, + sandbox, + sandbox_policy_cwd, + codex_linux_sandbox_exe, + use_linux_sandbox_bwrap, + windows_sandbox_level, + } = request; let mut env = spec.env; if !policy.has_full_network_access() { env.insert( @@ -141,8 +158,12 @@ impl SandboxManager { SandboxType::LinuxSeccomp => { let exe = codex_linux_sandbox_exe .ok_or(SandboxTransformError::MissingLinuxSandboxExecutable)?; - let mut args = - create_linux_sandbox_command_args(command.clone(), policy, sandbox_policy_cwd); + let mut args = create_linux_sandbox_command_args( + command.clone(), + policy, + sandbox_policy_cwd, + use_linux_sandbox_bwrap, + ); let mut full_command = Vec::with_capacity(1 + args.len()); full_command.push(exe.to_string_lossy().to_string()); full_command.append(&mut args); diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index fb2150953b..381f8ce136 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -8,6 +8,7 @@ retry without sandbox on denial (no re‑approval thanks to caching). use crate::error::CodexErr; use crate::error::SandboxErr; use crate::exec::ExecToolCallOutput; +use crate::features::Feature; use crate::sandboxing::SandboxManager; use crate::tools::sandboxing::ApprovalCtx; use crate::tools::sandboxing::ExecApprovalRequirement; @@ -97,12 +98,14 @@ impl ToolOrchestrator { // Platform-specific flag gating is handled by SandboxManager::select_initial // via crate::safety::get_platform_sandbox(..). + let use_linux_sandbox_bwrap = turn_ctx.features.enabled(Feature::UseLinuxSandboxBwrap); let initial_attempt = SandboxAttempt { sandbox: initial_sandbox, policy: &turn_ctx.sandbox_policy, manager: &self.sandbox, sandbox_cwd: &turn_ctx.cwd, codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(), + use_linux_sandbox_bwrap, windows_sandbox_level: turn_ctx.windows_sandbox_level, }; @@ -154,6 +157,7 @@ impl ToolOrchestrator { manager: &self.sandbox, sandbox_cwd: &turn_ctx.cwd, codex_linux_sandbox_exe: None, + use_linux_sandbox_bwrap, windows_sandbox_level: turn_ctx.windows_sandbox_level, }; diff --git a/codex-rs/core/src/tools/sandboxing.rs b/codex-rs/core/src/tools/sandboxing.rs index a7d2bca62a..d50e592530 100644 --- a/codex-rs/core/src/tools/sandboxing.rs +++ b/codex-rs/core/src/tools/sandboxing.rs @@ -274,6 +274,7 @@ pub(crate) struct SandboxAttempt<'a> { pub(crate) manager: &'a SandboxManager, pub(crate) sandbox_cwd: &'a Path, pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>, + pub use_linux_sandbox_bwrap: bool, pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, } @@ -282,14 +283,16 @@ impl<'a> SandboxAttempt<'a> { &self, spec: CommandSpec, ) -> Result { - self.manager.transform( - spec, - self.policy, - self.sandbox, - self.sandbox_cwd, - self.codex_linux_sandbox_exe, - self.windows_sandbox_level, - ) + self.manager + .transform(crate::sandboxing::SandboxTransformRequest { + spec, + policy: self.policy, + sandbox: self.sandbox, + sandbox_policy_cwd: self.sandbox_cwd, + codex_linux_sandbox_exe: self.codex_linux_sandbox_exe, + use_linux_sandbox_bwrap: self.use_linux_sandbox_bwrap, + windows_sandbox_level: self.windows_sandbox_level, + }) } } diff --git a/codex-rs/core/tests/suite/exec.rs b/codex-rs/core/tests/suite/exec.rs index cdf597a4e9..b70062d7ed 100644 --- a/codex-rs/core/tests/suite/exec.rs +++ b/codex-rs/core/tests/suite/exec.rs @@ -44,7 +44,7 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result `. +- When enabled, protected subpaths under writable roots (for example `.git`, + resolved `gitdir:`, and `.codex`) are re-applied as read-only via `--ro-bind`. +- When enabled, symlink-in-path and non-existent protected paths inside + writable roots are blocked by mounting `/dev/null` on the symlink or first + missing component. +- When enabled, the helper isolates the PID namespace via `--unshare-pid`. +- When enabled, it mounts a fresh `/proc` via `--proc /proc` by default, but + you can skip this in restrictive container environments with `--no-proc`. + +**Notes** +- The CLI surface still uses legacy names like `codex debug landlock`. diff --git a/codex-rs/linux-sandbox/src/bwrap.rs b/codex-rs/linux-sandbox/src/bwrap.rs index c1e0732e08..4a60c65dd3 100644 --- a/codex-rs/linux-sandbox/src/bwrap.rs +++ b/codex-rs/linux-sandbox/src/bwrap.rs @@ -44,45 +44,6 @@ pub(crate) fn create_bwrap_command_args( sandbox_policy: &SandboxPolicy, cwd: &Path, options: BwrapOptions, - bwrap_path: Option<&Path>, -) -> Result> { - if sandbox_policy.has_full_disk_write_access() { - return Ok(command); - } - - let bwrap_path = match bwrap_path { - Some(path) => { - if path.exists() { - path.to_path_buf() - } else { - return Err(CodexErr::UnsupportedOperation(format!( - "bubblewrap (bwrap) not found at configured path: {}", - path.display() - ))); - } - } - None => which::which("bwrap").map_err(|err| { - CodexErr::UnsupportedOperation(format!("bubblewrap (bwrap) not found on PATH: {err}")) - })?, - }; - - let mut args = Vec::new(); - args.push(path_to_string(&bwrap_path)); - args.extend(create_bwrap_flags(command, sandbox_policy, cwd, options)?); - Ok(args) -} - -/// Doc-hidden helper that builds bubblewrap arguments without a program path. -/// -/// This is intended for experiments where we call a build-time bubblewrap -/// `main` symbol via FFI rather than exec'ing the `bwrap` binary. The caller -/// is responsible for providing a suitable `argv[0]`. -#[doc(hidden)] -pub(crate) fn create_bwrap_command_args_vendored( - command: Vec, - sandbox_policy: &SandboxPolicy, - cwd: &Path, - options: BwrapOptions, ) -> Result> { if sandbox_policy.has_full_disk_write_access() { return Ok(command); diff --git a/codex-rs/linux-sandbox/src/landlock.rs b/codex-rs/linux-sandbox/src/landlock.rs index 772176fc90..4853011792 100644 --- a/codex-rs/linux-sandbox/src/landlock.rs +++ b/codex-rs/linux-sandbox/src/landlock.rs @@ -1,3 +1,7 @@ +//! In-process Linux sandbox primitives: `no_new_privs` and seccomp. +//! +//! Filesystem restrictions are enforced by bubblewrap in `linux_run_main`. +//! Landlock helpers remain available here as legacy/backup utilities. use std::collections::BTreeMap; use std::path::Path; @@ -8,6 +12,7 @@ use codex_core::protocol::SandboxPolicy; use codex_utils_absolute_path::AbsolutePathBuf; use landlock::ABI; +#[allow(unused_imports)] use landlock::Access; use landlock::AccessFs; use landlock::CompatLevel; @@ -27,11 +32,24 @@ use seccompiler::apply_filter; /// Apply sandbox policies inside this thread so only the child inherits /// them, not the entire CLI process. +/// +/// This function is responsible for: +/// - enabling `PR_SET_NO_NEW_PRIVS` when restrictions apply, and +/// - installing the network seccomp filter when network access is disabled. +/// +/// Filesystem restrictions are intentionally handled by bubblewrap. pub(crate) fn apply_sandbox_policy_to_current_thread( sandbox_policy: &SandboxPolicy, cwd: &Path, + apply_landlock_fs: bool, ) -> Result<()> { - if !sandbox_policy.has_full_disk_write_access() || !sandbox_policy.has_full_network_access() { + // `PR_SET_NO_NEW_PRIVS` is required for seccomp, but it also prevents + // setuid privilege elevation. Many `bwrap` deployments rely on setuid, so + // we avoid this unless we need seccomp or we are explicitly using the + // legacy Landlock filesystem pipeline. + if !sandbox_policy.has_full_network_access() + || (apply_landlock_fs && !sandbox_policy.has_full_disk_write_access()) + { set_no_new_privs()?; } @@ -39,7 +57,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread( install_network_seccomp_filter_on_current_thread()?; } - if !sandbox_policy.has_full_disk_write_access() { + if apply_landlock_fs && !sandbox_policy.has_full_disk_write_access() { let writable_roots = sandbox_policy .get_writable_roots_with_cwd(cwd) .into_iter() @@ -54,6 +72,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread( Ok(()) } +/// Enable `PR_SET_NO_NEW_PRIVS` so seccomp can be applied safely. fn set_no_new_privs() -> Result<()> { let result = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) }; if result != 0 { @@ -68,6 +87,9 @@ fn set_no_new_privs() -> Result<()> { /// /// # Errors /// Returns [`CodexErr::Sandbox`] variants when the ruleset fails to apply. +/// +/// Note: this is currently unused because filesystem sandboxing is performed +/// via bubblewrap. It is kept for reference and potential fallback use. fn install_filesystem_landlock_rules_on_current_thread( writable_roots: Vec, ) -> Result<()> { @@ -98,6 +120,9 @@ fn install_filesystem_landlock_rules_on_current_thread( /// Installs a seccomp filter that blocks outbound network access except for /// AF_UNIX domain sockets. +/// +/// The filter is applied to the current thread so only the sandboxed child +/// inherits it. fn install_network_seccomp_filter_on_current_thread() -> std::result::Result<(), SandboxErr> { // Build rule map. let mut rules: BTreeMap> = BTreeMap::new(); diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index 3f6cd3ac86..f5f0d9887a 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -1,13 +1,16 @@ use clap::Parser; use std::ffi::CString; +use std::fs::File; +use std::io::Read; +use std::os::fd::FromRawFd; use std::path::Path; use std::path::PathBuf; use crate::bwrap::BwrapOptions; use crate::bwrap::create_bwrap_command_args; -use crate::bwrap::create_bwrap_command_args_vendored; use crate::landlock::apply_sandbox_policy_to_current_thread; use crate::vendored_bwrap::exec_vendored_bwrap; +use crate::vendored_bwrap::run_vendored_bwrap_main; #[derive(Debug, Parser)] /// CLI surface for the Linux sandbox helper. @@ -29,18 +32,6 @@ pub struct LandlockCommand { #[arg(long = "use-bwrap-sandbox", hide = true, default_value_t = false)] pub use_bwrap_sandbox: bool, - /// Optional explicit path to the `bwrap` binary to use. - /// - /// When provided, this implies bubblewrap opt-in and avoids PATH lookups. - #[arg(long = "bwrap-path", hide = true)] - pub bwrap_path: Option, - - /// Experimental: call a build-time bubblewrap `main()` via FFI. - /// - /// This is opt-in and only works when the build script compiles bwrap. - #[arg(long = "use-vendored-bwrap", hide = true, default_value_t = false)] - pub use_vendored_bwrap: bool, - /// Internal: apply seccomp and `no_new_privs` in the already-sandboxed /// process, then exec the user command. /// @@ -72,13 +63,10 @@ pub fn run_main() -> ! { sandbox_policy_cwd, sandbox_policy, use_bwrap_sandbox, - bwrap_path, - use_vendored_bwrap, apply_seccomp_then_exec, no_proc, command, } = LandlockCommand::parse(); - let use_bwrap_sandbox = use_bwrap_sandbox || bwrap_path.is_some() || use_vendored_bwrap; if command.is_empty() { panic!("No command specified to execute."); @@ -87,82 +75,199 @@ pub fn run_main() -> ! { // Inner stage: apply seccomp/no_new_privs after bubblewrap has already // established the filesystem view. if apply_seccomp_then_exec { - if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) + if let Err(e) = + apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false) { panic!("error applying Linux sandbox restrictions: {e:?}"); } exec_or_panic(command); } - let command = if sandbox_policy.has_full_disk_write_access() { - if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) + if sandbox_policy.has_full_disk_write_access() { + if let Err(e) = + apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, false) { panic!("error applying Linux sandbox restrictions: {e:?}"); } - command - } else if use_bwrap_sandbox { + exec_or_panic(command); + } + + if use_bwrap_sandbox { // Outer stage: bubblewrap first, then re-enter this binary in the - // sandboxed environment to apply seccomp. + // sandboxed environment to apply seccomp. This path never falls back + // to legacy Landlock on failure. let inner = build_inner_seccomp_command( &sandbox_policy_cwd, &sandbox_policy, use_bwrap_sandbox, - bwrap_path.as_deref(), command, ); - let options = BwrapOptions { - mount_proc: !no_proc, - }; - if use_vendored_bwrap { - let mut argv0 = bwrap_path - .as_deref() - .map(|path| path.to_string_lossy().to_string()) - .unwrap_or_else(|| "bwrap".to_string()); - if argv0.is_empty() { - argv0 = "bwrap".to_string(); - } - - let mut argv = vec![argv0]; - argv.extend( - create_bwrap_command_args_vendored( - inner, - &sandbox_policy, - &sandbox_policy_cwd, - options, - ) - .unwrap_or_else(|err| { - panic!("error building build-time bubblewrap command: {err:?}") - }), - ); - exec_vendored_bwrap(argv); - } - ensure_bwrap_available(bwrap_path.as_deref()); - create_bwrap_command_args( - inner, - &sandbox_policy, - &sandbox_policy_cwd, - options, - bwrap_path.as_deref(), - ) - .unwrap_or_else(|err| panic!("error building bubblewrap command: {err:?}")) - } else { - // Legacy path: Landlock enforcement only. - if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd) - { - panic!("error applying legacy Linux sandbox restrictions: {e:?}"); - } - command - }; + run_bwrap_with_proc_fallback(&sandbox_policy_cwd, &sandbox_policy, inner, !no_proc); + } + // Legacy path: Landlock enforcement only, when bwrap sandboxing is not enabled. + if let Err(e) = + apply_sandbox_policy_to_current_thread(&sandbox_policy, &sandbox_policy_cwd, true) + { + panic!("error applying legacy Linux sandbox restrictions: {e:?}"); + } exec_or_panic(command); } +fn run_bwrap_with_proc_fallback( + sandbox_policy_cwd: &Path, + sandbox_policy: &codex_core::protocol::SandboxPolicy, + inner: Vec, + mount_proc: bool, +) -> ! { + let mut mount_proc = mount_proc; + + if mount_proc && !preflight_proc_mount_support(sandbox_policy_cwd, sandbox_policy) { + eprintln!("codex-linux-sandbox: bwrap could not mount /proc; retrying with --no-proc"); + mount_proc = false; + } + + let options = BwrapOptions { mount_proc }; + let argv = build_bwrap_argv(inner, sandbox_policy, sandbox_policy_cwd, options); + exec_vendored_bwrap(argv); +} + +fn build_bwrap_argv( + inner: Vec, + sandbox_policy: &codex_core::protocol::SandboxPolicy, + sandbox_policy_cwd: &Path, + options: BwrapOptions, +) -> Vec { + let mut args = create_bwrap_command_args(inner, sandbox_policy, sandbox_policy_cwd, options) + .unwrap_or_else(|err| panic!("error building bubblewrap command: {err:?}")); + + let command_separator_index = args + .iter() + .position(|arg| arg == "--") + .unwrap_or_else(|| panic!("bubblewrap argv is missing command separator '--'")); + args.splice( + command_separator_index..command_separator_index, + ["--argv0".to_string(), "codex-linux-sandbox".to_string()], + ); + + let mut argv = vec!["bwrap".to_string()]; + argv.extend(args); + argv +} + +fn preflight_proc_mount_support( + sandbox_policy_cwd: &Path, + sandbox_policy: &codex_core::protocol::SandboxPolicy, +) -> bool { + let preflight_command = vec![resolve_true_command()]; + let preflight_argv = build_bwrap_argv( + preflight_command, + sandbox_policy, + sandbox_policy_cwd, + BwrapOptions { mount_proc: true }, + ); + let stderr = run_bwrap_in_child_capture_stderr(preflight_argv); + !is_proc_mount_failure(stderr.as_str()) +} + +fn resolve_true_command() -> String { + for candidate in ["/usr/bin/true", "/bin/true"] { + if Path::new(candidate).exists() { + return candidate.to_string(); + } + } + "true".to_string() +} + +/// Run a short-lived bubblewrap preflight in a child process and capture stderr. +/// +/// Strategy: +/// - This is used only by `preflight_proc_mount_support`, which runs `/bin/true` +/// under bubblewrap with `--proc /proc`. +/// - The goal is to detect environments where mounting `/proc` fails (for +/// example, restricted containers), so we can retry the real run with +/// `--no-proc`. +/// - We capture stderr from that preflight to match known mount-failure text. +/// We do not stream it because this is a one-shot probe with a trivial +/// command, and reads are bounded to a fixed max size. +fn run_bwrap_in_child_capture_stderr(argv: Vec) -> String { + const MAX_PREFLIGHT_STDERR_BYTES: u64 = 64 * 1024; + + let mut pipe_fds = [0; 2]; + let pipe_res = unsafe { libc::pipe2(pipe_fds.as_mut_ptr(), libc::O_CLOEXEC) }; + if pipe_res < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to create stderr pipe for bubblewrap: {err}"); + } + let read_fd = pipe_fds[0]; + let write_fd = pipe_fds[1]; + + let pid = unsafe { libc::fork() }; + if pid < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to fork for bubblewrap: {err}"); + } + + if pid == 0 { + // Child: redirect stderr to the pipe, then run bubblewrap. + unsafe { + close_fd_or_panic(read_fd, "close read end in bubblewrap child"); + if libc::dup2(write_fd, libc::STDERR_FILENO) < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to redirect stderr for bubblewrap: {err}"); + } + close_fd_or_panic(write_fd, "close write end in bubblewrap child"); + } + + let exit_code = run_vendored_bwrap_main(&argv); + std::process::exit(exit_code); + } + + // Parent: close the write end and read stderr while the child runs. + close_fd_or_panic(write_fd, "close write end in bubblewrap parent"); + + // SAFETY: `read_fd` is a valid owned fd in the parent. + let mut read_file = unsafe { File::from_raw_fd(read_fd) }; + let mut stderr_bytes = Vec::new(); + let mut limited_reader = (&mut read_file).take(MAX_PREFLIGHT_STDERR_BYTES); + if let Err(err) = limited_reader.read_to_end(&mut stderr_bytes) { + panic!("failed to read bubblewrap stderr: {err}"); + } + + let mut status: libc::c_int = 0; + let wait_res = unsafe { libc::waitpid(pid, &mut status as *mut libc::c_int, 0) }; + if wait_res < 0 { + let err = std::io::Error::last_os_error(); + panic!("waitpid failed for bubblewrap child: {err}"); + } + + String::from_utf8_lossy(&stderr_bytes).into_owned() +} + +/// Close an owned file descriptor and panic with context on failure. +/// +/// We use explicit close() checks here (instead of ignoring return codes) +/// because this code runs in low-level sandbox setup paths where fd leaks or +/// close errors can mask the root cause of later failures. +fn close_fd_or_panic(fd: libc::c_int, context: &str) { + let close_res = unsafe { libc::close(fd) }; + if close_res < 0 { + let err = std::io::Error::last_os_error(); + panic!("{context}: {err}"); + } +} + +fn is_proc_mount_failure(stderr: &str) -> bool { + stderr.contains("Can't mount proc") + && stderr.contains("/newroot/proc") + && stderr.contains("Invalid argument") +} + /// Build the inner command that applies seccomp after bubblewrap. fn build_inner_seccomp_command( sandbox_policy_cwd: &Path, sandbox_policy: &codex_core::protocol::SandboxPolicy, use_bwrap_sandbox: bool, - bwrap_path: Option<&Path>, command: Vec, ) -> Vec { let current_exe = match std::env::current_exe() { @@ -185,10 +290,6 @@ fn build_inner_seccomp_command( inner.push("--use-bwrap-sandbox".to_string()); inner.push("--apply-seccomp-then-exec".to_string()); } - if let Some(bwrap_path) = bwrap_path { - inner.push("--bwrap-path".to_string()); - inner.push(bwrap_path.to_string_lossy().to_string()); - } inner.push("--".to_string()); inner.extend(command); inner @@ -217,32 +318,52 @@ fn exec_or_panic(command: Vec) -> ! { panic!("Failed to execvp {}: {err}", command[0].as_str()); } -/// Ensure the `bwrap` binary is available when the sandbox needs it. -fn ensure_bwrap_available(bwrap_path: Option<&Path>) { - if let Some(path) = bwrap_path { - if path.exists() { - return; - } - panic!( - "bubblewrap (bwrap) is required for Linux filesystem sandboxing but was not found at the configured path: {}\n\ -Install it and retry. Examples:\n\ -- Debian/Ubuntu: apt-get install bubblewrap\n\ -- Fedora/RHEL: dnf install bubblewrap\n\ -- Arch: pacman -S bubblewrap\n\ -If you are running the Codex Node package, ensure bwrap is installed on the host system.", - path.display() - ); - } - if which::which("bwrap").is_ok() { - return; +#[cfg(test)] +mod tests { + use super::*; + use codex_core::protocol::SandboxPolicy; + use pretty_assertions::assert_eq; + + #[test] + fn detects_proc_mount_invalid_argument_failure() { + let stderr = "bwrap: Can't mount proc on /newroot/proc: Invalid argument"; + assert_eq!(is_proc_mount_failure(stderr), true); } - panic!( - "bubblewrap (bwrap) is required for Linux filesystem sandboxing but was not found on PATH.\n\ -Install it and retry. Examples:\n\ -- Debian/Ubuntu: apt-get install bubblewrap\n\ -- Fedora/RHEL: dnf install bubblewrap\n\ -- Arch: pacman -S bubblewrap\n\ -If you are running the Codex Node package, ensure bwrap is installed on the host system." - ); + #[test] + fn ignores_non_proc_mount_errors() { + let stderr = "bwrap: Can't bind mount /dev/null: Operation not permitted"; + assert_eq!(is_proc_mount_failure(stderr), false); + } + + #[test] + fn inserts_bwrap_argv0_before_command_separator() { + let argv = build_bwrap_argv( + vec!["/bin/true".to_string()], + &SandboxPolicy::ReadOnly, + Path::new("/"), + BwrapOptions { mount_proc: true }, + ); + assert_eq!( + argv, + vec![ + "bwrap".to_string(), + "--new-session".to_string(), + "--die-with-parent".to_string(), + "--ro-bind".to_string(), + "/".to_string(), + "/".to_string(), + "--dev-bind".to_string(), + "/dev/null".to_string(), + "/dev/null".to_string(), + "--unshare-pid".to_string(), + "--proc".to_string(), + "/proc".to_string(), + "--argv0".to_string(), + "codex-linux-sandbox".to_string(), + "--".to_string(), + "/bin/true".to_string(), + ] + ); + } } diff --git a/codex-rs/linux-sandbox/src/mounts.rs b/codex-rs/linux-sandbox/src/mounts.rs deleted file mode 100644 index 04f3651dff..0000000000 --- a/codex-rs/linux-sandbox/src/mounts.rs +++ /dev/null @@ -1,339 +0,0 @@ -#![allow(dead_code)] - -use std::ffi::CString; -use std::os::unix::ffi::OsStrExt; -use std::path::Path; - -use codex_core::error::CodexErr; -use codex_core::error::Result; -use codex_core::protocol::SandboxPolicy; -use codex_core::protocol::WritableRoot; -use codex_utils_absolute_path::AbsolutePathBuf; - -/// Apply read-only bind mounts for protected subpaths before Landlock. -/// -/// This unshares mount namespaces (and user namespaces for non-root) so the -/// read-only remounts do not affect the host, then bind-mounts each protected -/// target onto itself and remounts it read-only. -pub(crate) fn apply_read_only_mounts(sandbox_policy: &SandboxPolicy, cwd: &Path) -> Result<()> { - let writable_roots = sandbox_policy.get_writable_roots_with_cwd(cwd); - let mount_targets = collect_read_only_mount_targets(&writable_roots)?; - if mount_targets.is_empty() { - return Ok(()); - } - - // Root can unshare the mount namespace directly; non-root needs a user - // namespace to gain capabilities for remounting. - if is_running_as_root() { - unshare_mount_namespace()?; - } else { - let original_euid = unsafe { libc::geteuid() }; - let original_egid = unsafe { libc::getegid() }; - unshare_user_and_mount_namespaces()?; - write_user_namespace_maps(original_euid, original_egid)?; - } - make_mounts_private()?; - - for target in mount_targets { - // Bind and remount read-only works for both files and directories. - bind_mount_read_only(target.as_path())?; - } - - // Drop ambient capabilities acquired from the user namespace so the - // sandboxed command cannot remount or create new bind mounts. - if !is_running_as_root() { - drop_caps()?; - } - - Ok(()) -} - -/// Collect read-only mount targets, resolving worktree `.git` pointer files. -fn collect_read_only_mount_targets( - writable_roots: &[WritableRoot], -) -> Result> { - let mut targets = Vec::new(); - for writable_root in writable_roots { - for ro_subpath in &writable_root.read_only_subpaths { - // The policy expects these paths to exist; surface actionable errors - // rather than silently skipping protections. - if !ro_subpath.as_path().exists() { - return Err(CodexErr::UnsupportedOperation(format!( - "Sandbox expected to protect {path}, but it does not exist. Ensure the repository contains this path or create it before running Codex.", - path = ro_subpath.as_path().display() - ))); - } - targets.push(ro_subpath.clone()); - // Worktrees and submodules store `.git` as a pointer file; add the - // referenced gitdir as an extra read-only target. - if is_git_pointer_file(ro_subpath) { - let gitdir = resolve_gitdir_from_file(ro_subpath)?; - if !targets - .iter() - .any(|target| target.as_path() == gitdir.as_path()) - { - targets.push(gitdir); - } - } - } - } - Ok(targets) -} - -/// Detect a `.git` pointer file used by worktrees and submodules. -fn is_git_pointer_file(path: &AbsolutePathBuf) -> bool { - path.as_path().is_file() && path.as_path().file_name() == Some(std::ffi::OsStr::new(".git")) -} - -/// Resolve a worktree `.git` pointer file to its gitdir path. -fn resolve_gitdir_from_file(dot_git: &AbsolutePathBuf) -> Result { - let contents = std::fs::read_to_string(dot_git.as_path()).map_err(CodexErr::from)?; - let trimmed = contents.trim(); - let (_, gitdir_raw) = trimmed.split_once(':').ok_or_else(|| { - CodexErr::UnsupportedOperation(format!( - "Expected {path} to contain a gitdir pointer, but it did not match `gitdir: `.", - path = dot_git.as_path().display() - )) - })?; - // `gitdir: ` may be relative to the directory containing `.git`. - let gitdir_raw = gitdir_raw.trim(); - if gitdir_raw.is_empty() { - return Err(CodexErr::UnsupportedOperation(format!( - "Expected {path} to contain a gitdir pointer, but it was empty.", - path = dot_git.as_path().display() - ))); - } - let base = dot_git.as_path().parent().ok_or_else(|| { - CodexErr::UnsupportedOperation(format!( - "Unable to resolve parent directory for {path}.", - path = dot_git.as_path().display() - )) - })?; - let gitdir_path = AbsolutePathBuf::resolve_path_against_base(gitdir_raw, base)?; - if !gitdir_path.as_path().exists() { - return Err(CodexErr::UnsupportedOperation(format!( - "Resolved gitdir path {path} does not exist.", - path = gitdir_path.as_path().display() - ))); - } - Ok(gitdir_path) -} - -/// Unshare the mount namespace so mount changes are isolated to the sandboxed process. -fn unshare_mount_namespace() -> Result<()> { - let result = unsafe { libc::unshare(libc::CLONE_NEWNS) }; - if result != 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) -} - -/// Unshare user + mount namespaces so the process can remount read-only without privileges. -fn unshare_user_and_mount_namespaces() -> Result<()> { - let result = unsafe { libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWNS) }; - if result != 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) -} - -fn is_running_as_root() -> bool { - unsafe { libc::geteuid() == 0 } -} - -#[repr(C)] -struct CapUserHeader { - version: u32, - pid: i32, -} - -#[repr(C)] -struct CapUserData { - effective: u32, - permitted: u32, - inheritable: u32, -} - -const LINUX_CAPABILITY_VERSION_3: u32 = 0x2008_0522; - -/// Map the provided uid/gid to root inside the user namespace. -fn write_user_namespace_maps(uid: libc::uid_t, gid: libc::gid_t) -> Result<()> { - write_proc_file("/proc/self/setgroups", "deny\n")?; - - write_proc_file("/proc/self/uid_map", format!("0 {uid} 1\n"))?; - write_proc_file("/proc/self/gid_map", format!("0 {gid} 1\n"))?; - Ok(()) -} - -/// Drop all capabilities in the current user namespace. -fn drop_caps() -> Result<()> { - let mut header = CapUserHeader { - version: LINUX_CAPABILITY_VERSION_3, - pid: 0, - }; - let data = [ - CapUserData { - effective: 0, - permitted: 0, - inheritable: 0, - }, - CapUserData { - effective: 0, - permitted: 0, - inheritable: 0, - }, - ]; - - // Use syscall directly to avoid libc capability symbols that are missing on musl. - let result = unsafe { libc::syscall(libc::SYS_capset, &mut header, data.as_ptr()) }; - if result != 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) -} - -/// Write a small procfs file, returning a sandbox error on failure. -fn write_proc_file(path: &str, contents: impl AsRef<[u8]>) -> Result<()> { - std::fs::write(path, contents)?; - Ok(()) -} - -/// Ensure mounts are private so remounting does not propagate outside the namespace. -fn make_mounts_private() -> Result<()> { - let root = CString::new("/").map_err(|_| { - CodexErr::UnsupportedOperation("Sandbox mount path contains NUL byte: /".to_string()) - })?; - let result = unsafe { - libc::mount( - std::ptr::null(), - root.as_ptr(), - std::ptr::null(), - libc::MS_REC | libc::MS_PRIVATE, - std::ptr::null(), - ) - }; - if result != 0 { - return Err(std::io::Error::last_os_error().into()); - } - Ok(()) -} - -/// Bind-mount a path onto itself and remount read-only. -fn bind_mount_read_only(path: &Path) -> Result<()> { - let c_path = CString::new(path.as_os_str().as_bytes()).map_err(|_| { - CodexErr::UnsupportedOperation(format!( - "Sandbox mount path contains NUL byte: {path}", - path = path.display() - )) - })?; - - let bind_result = unsafe { - libc::mount( - c_path.as_ptr(), - c_path.as_ptr(), - std::ptr::null(), - libc::MS_BIND, - std::ptr::null(), - ) - }; - if bind_result != 0 { - return Err(std::io::Error::last_os_error().into()); - } - - let remount_result = unsafe { - libc::mount( - c_path.as_ptr(), - c_path.as_ptr(), - std::ptr::null(), - libc::MS_BIND | libc::MS_REMOUNT | libc::MS_RDONLY, - std::ptr::null(), - ) - }; - if remount_result != 0 { - return Err(std::io::Error::last_os_error().into()); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn collect_read_only_mount_targets_errors_on_missing_path() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let missing = AbsolutePathBuf::try_from(tempdir.path().join("missing").as_path()) - .expect("missing path"); - let root = AbsolutePathBuf::try_from(tempdir.path()).expect("root"); - let writable_root = WritableRoot { - root, - read_only_subpaths: vec![missing], - }; - - let err = collect_read_only_mount_targets(&[writable_root]) - .expect_err("expected missing path error"); - let message = match err { - CodexErr::UnsupportedOperation(message) => message, - other => panic!("unexpected error: {other:?}"), - }; - assert_eq!( - message, - format!( - "Sandbox expected to protect {path}, but it does not exist. Ensure the repository contains this path or create it before running Codex.", - path = tempdir.path().join("missing").display() - ) - ); - } - - #[test] - fn collect_read_only_mount_targets_adds_gitdir_for_pointer_file() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let gitdir = tempdir.path().join("actual-gitdir"); - std::fs::create_dir_all(&gitdir).expect("create gitdir"); - let dot_git = tempdir.path().join(".git"); - std::fs::write(&dot_git, format!("gitdir: {}\n", gitdir.display())) - .expect("write gitdir pointer"); - let root = AbsolutePathBuf::try_from(tempdir.path()).expect("root"); - let writable_root = WritableRoot { - root, - read_only_subpaths: vec![ - AbsolutePathBuf::try_from(dot_git.as_path()).expect("dot git"), - ], - }; - - let targets = collect_read_only_mount_targets(&[writable_root]).expect("collect targets"); - assert_eq!(targets.len(), 2); - assert_eq!(targets[0].as_path(), dot_git.as_path()); - assert_eq!(targets[1].as_path(), gitdir.as_path()); - } - - #[test] - fn collect_read_only_mount_targets_errors_on_invalid_gitdir_pointer() { - let tempdir = tempfile::tempdir().expect("tempdir"); - let dot_git = tempdir.path().join(".git"); - std::fs::write(&dot_git, "not-a-pointer\n").expect("write invalid pointer"); - let root = AbsolutePathBuf::try_from(tempdir.path()).expect("root"); - let writable_root = WritableRoot { - root, - read_only_subpaths: vec![ - AbsolutePathBuf::try_from(dot_git.as_path()).expect("dot git"), - ], - }; - - let err = collect_read_only_mount_targets(&[writable_root]) - .expect_err("expected invalid pointer error"); - let message = match err { - CodexErr::UnsupportedOperation(message) => message, - other => panic!("unexpected error: {other:?}"), - }; - assert_eq!( - message, - format!( - "Expected {path} to contain a gitdir pointer, but it did not match `gitdir: `.", - path = dot_git.display() - ) - ); - } -} diff --git a/codex-rs/linux-sandbox/src/vendored_bwrap.rs b/codex-rs/linux-sandbox/src/vendored_bwrap.rs index ab4fb959ef..c2816061ea 100644 --- a/codex-rs/linux-sandbox/src/vendored_bwrap.rs +++ b/codex-rs/linux-sandbox/src/vendored_bwrap.rs @@ -13,22 +13,35 @@ mod imp { fn bwrap_main(argc: libc::c_int, argv: *const *const c_char) -> libc::c_int; } - /// Execute the build-time bubblewrap `main` function with the given argv. - pub(crate) fn exec_vendored_bwrap(argv: Vec) -> ! { + fn argv_to_cstrings(argv: &[String]) -> Vec { let mut cstrings: Vec = Vec::with_capacity(argv.len()); - for arg in &argv { + for arg in argv { match CString::new(arg.as_str()) { Ok(value) => cstrings.push(value), Err(err) => panic!("failed to convert argv to CString: {err}"), } } + cstrings + } + + /// Run the build-time bubblewrap `main` function and return its exit code. + /// + /// On success, bubblewrap will `execve` into the target program and this + /// function will never return. A return value therefore implies failure. + pub(crate) fn run_vendored_bwrap_main(argv: &[String]) -> libc::c_int { + let cstrings = argv_to_cstrings(argv); let mut argv_ptrs: Vec<*const c_char> = cstrings.iter().map(|arg| arg.as_ptr()).collect(); argv_ptrs.push(std::ptr::null()); // SAFETY: We provide a null-terminated argv vector whose pointers // remain valid for the duration of the call. - let exit_code = unsafe { bwrap_main(cstrings.len() as libc::c_int, argv_ptrs.as_ptr()) }; + unsafe { bwrap_main(cstrings.len() as libc::c_int, argv_ptrs.as_ptr()) } + } + + /// Execute the build-time bubblewrap `main` function with the given argv. + pub(crate) fn exec_vendored_bwrap(argv: Vec) -> ! { + let exit_code = run_vendored_bwrap_main(&argv); std::process::exit(exit_code); } } @@ -36,7 +49,7 @@ mod imp { #[cfg(not(vendored_bwrap_available))] mod imp { /// Panics with a clear error when the build-time bwrap path is not enabled. - pub(crate) fn exec_vendored_bwrap(_argv: Vec) -> ! { + pub(crate) fn run_vendored_bwrap_main(_argv: &[String]) -> libc::c_int { panic!( "build-time bubblewrap is not available in this build.\n\ Rebuild codex-linux-sandbox on Linux with CODEX_BWRAP_ENABLE_FFI=1.\n\ @@ -49,6 +62,13 @@ Notes:\n\ - bubblewrap sources expected at codex-rs/vendor/bubblewrap (default)" ); } + + /// Panics with a clear error when the build-time bwrap path is not enabled. + pub(crate) fn exec_vendored_bwrap(_argv: Vec) -> ! { + let _ = run_vendored_bwrap_main(&[]); + unreachable!("run_vendored_bwrap_main should always panic in this configuration") + } } pub(crate) use imp::exec_vendored_bwrap; +pub(crate) use imp::run_vendored_bwrap_main; diff --git a/codex-rs/linux-sandbox/tests/suite/landlock.rs b/codex-rs/linux-sandbox/tests/suite/landlock.rs index 75551d4d91..52cd402a13 100644 --- a/codex-rs/linux-sandbox/tests/suite/landlock.rs +++ b/codex-rs/linux-sandbox/tests/suite/landlock.rs @@ -2,6 +2,7 @@ #![allow(clippy::unwrap_used)] use codex_core::config::types::ShellEnvironmentPolicy; use codex_core::error::CodexErr; +use codex_core::error::Result; use codex_core::error::SandboxErr; use codex_core::exec::ExecParams; use codex_core::exec::process_exec_tool_call; @@ -32,6 +33,8 @@ const NETWORK_TIMEOUT_MS: u64 = 2_000; #[cfg(target_arch = "aarch64")] const NETWORK_TIMEOUT_MS: u64 = 10_000; +const BWRAP_UNAVAILABLE_ERR: &str = "build-time bubblewrap is not available in this build."; + fn create_env_from_core_vars() -> HashMap { let policy = ShellEnvironmentPolicy::default(); create_env(&policy, None) @@ -47,12 +50,24 @@ async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) { } } -#[expect(clippy::expect_used, clippy::unwrap_used)] +#[expect(clippy::expect_used)] async fn run_cmd_output( cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64, ) -> codex_core::exec::ExecToolCallOutput { + run_cmd_result_with_writable_roots(cmd, writable_roots, timeout_ms, false) + .await + .expect("sandboxed command should execute") +} + +#[expect(clippy::expect_used)] +async fn run_cmd_result_with_writable_roots( + cmd: &[&str], + writable_roots: &[PathBuf], + timeout_ms: u64, + use_bwrap_sandbox: bool, +) -> Result { let cwd = std::env::current_dir().expect("cwd should exist"); let sandbox_cwd = cwd.clone(); let params = ExecParams { @@ -86,10 +101,48 @@ async fn run_cmd_output( &sandbox_policy, sandbox_cwd.as_path(), &codex_linux_sandbox_exe, + use_bwrap_sandbox, None, ) .await - .unwrap() +} + +fn is_bwrap_unavailable_output(output: &codex_core::exec::ExecToolCallOutput) -> bool { + output.stderr.text.contains(BWRAP_UNAVAILABLE_ERR) +} + +async fn should_skip_bwrap_tests() -> bool { + match run_cmd_result_with_writable_roots( + &["bash", "-lc", "true"], + &[], + NETWORK_TIMEOUT_MS, + true, + ) + .await + { + Ok(output) => is_bwrap_unavailable_output(&output), + Err(CodexErr::Sandbox(SandboxErr::Denied { output })) => { + is_bwrap_unavailable_output(&output) + } + // Probe timeouts are not actionable for the bwrap-specific assertions below; + // skip rather than fail the whole suite. + Err(CodexErr::Sandbox(SandboxErr::Timeout { .. })) => true, + Err(err) => panic!("bwrap availability probe failed unexpectedly: {err:?}"), + } +} + +fn expect_denied( + result: Result, + context: &str, +) -> codex_core::exec::ExecToolCallOutput { + match result { + Ok(output) => { + assert_ne!(output.exit_code, 0, "{context}: expected nonzero exit code"); + output + } + Err(CodexErr::Sandbox(SandboxErr::Denied { output })) => *output, + Err(err) => panic!("{context}: {err:?}"), + } } #[tokio::test] @@ -192,6 +245,7 @@ async fn assert_network_blocked(cmd: &[&str]) { &sandbox_policy, sandbox_cwd.as_path(), &codex_linux_sandbox_exe, + false, None, ) .await; @@ -242,6 +296,90 @@ async fn sandbox_blocks_nc() { assert_network_blocked(&["nc", "-z", "127.0.0.1", "80"]).await; } +#[tokio::test] +async fn sandbox_blocks_git_and_codex_writes_inside_writable_root() { + if should_skip_bwrap_tests().await { + eprintln!("skipping bwrap test: vendored bwrap was not built in this environment"); + return; + } + + let tmpdir = tempfile::tempdir().expect("tempdir"); + let dot_git = tmpdir.path().join(".git"); + let dot_codex = tmpdir.path().join(".codex"); + std::fs::create_dir_all(&dot_git).expect("create .git"); + std::fs::create_dir_all(&dot_codex).expect("create .codex"); + + let git_target = dot_git.join("config"); + let codex_target = dot_codex.join("config.toml"); + + let git_output = expect_denied( + run_cmd_result_with_writable_roots( + &[ + "bash", + "-lc", + &format!("echo denied > {}", git_target.to_string_lossy()), + ], + &[tmpdir.path().to_path_buf()], + LONG_TIMEOUT_MS, + true, + ) + .await, + ".git write should be denied under bubblewrap", + ); + + let codex_output = expect_denied( + run_cmd_result_with_writable_roots( + &[ + "bash", + "-lc", + &format!("echo denied > {}", codex_target.to_string_lossy()), + ], + &[tmpdir.path().to_path_buf()], + LONG_TIMEOUT_MS, + true, + ) + .await, + ".codex write should be denied under bubblewrap", + ); + assert_ne!(git_output.exit_code, 0); + assert_ne!(codex_output.exit_code, 0); +} + +#[tokio::test] +async fn sandbox_blocks_codex_symlink_replacement_attack() { + if should_skip_bwrap_tests().await { + eprintln!("skipping bwrap test: vendored bwrap was not built in this environment"); + return; + } + + use std::os::unix::fs::symlink; + + let tmpdir = tempfile::tempdir().expect("tempdir"); + let decoy = tmpdir.path().join("decoy-codex"); + std::fs::create_dir_all(&decoy).expect("create decoy dir"); + + let dot_codex = tmpdir.path().join(".codex"); + symlink(&decoy, &dot_codex).expect("create .codex symlink"); + + let codex_target = dot_codex.join("config.toml"); + + let codex_output = expect_denied( + run_cmd_result_with_writable_roots( + &[ + "bash", + "-lc", + &format!("echo denied > {}", codex_target.to_string_lossy()), + ], + &[tmpdir.path().to_path_buf()], + LONG_TIMEOUT_MS, + true, + ) + .await, + ".codex symlink replacement should be denied", + ); + assert_ne!(codex_output.exit_code, 0); +} + #[tokio::test] async fn sandbox_blocks_ssh() { // Force ssh to attempt a real TCP connection but fail quickly. `BatchMode` From 7a253076fef8058adb7964fdd521df80663cf39d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 4 Feb 2026 11:47:10 -0800 Subject: [PATCH 28/90] Persist pending input user events (#10656) - Persist user-message events for mid-turn injected input by emitting user message turn items when pending input is recorded. --- codex-rs/core/src/codex.rs | 28 +++++++++++++++++----- codex-rs/core/tests/suite/pending_input.rs | 5 ++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 5368b8b062..b1d2fdf45e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -3579,19 +3579,35 @@ pub(crate) async fn run_turn( // Note that pending_input would be something like a message the user // submitted through the UI while the model was running. Though the UI // may support this, the model might not. - let pending_input = sess + let pending_response_items = sess .get_pending_input() .await .into_iter() .map(ResponseItem::from) .collect::>(); + if !pending_response_items.is_empty() { + for response_item in pending_response_items { + if let Some(TurnItem::UserMessage(user_message)) = parse_turn_item(&response_item) { + // todo(aibrahim): move pending input to be UserInput only to keep TextElements. context: https://github.com/openai/codex/pull/10656#discussion_r2765522480 + sess.record_user_prompt_and_emit_turn_item( + turn_context.as_ref(), + &user_message.content, + response_item, + ) + .await; + } else { + sess.record_conversation_items( + &turn_context, + std::slice::from_ref(&response_item), + ) + .await; + } + } + } + // Construct the input that we will send to the model. - let sampling_request_input: Vec = { - sess.record_conversation_items(&turn_context, &pending_input) - .await; - sess.clone_history().await.for_prompt() - }; + let sampling_request_input: Vec = { sess.clone_history().await.for_prompt() }; let sampling_request_input_messages = sampling_request_input .iter() diff --git a/codex-rs/core/tests/suite/pending_input.rs b/codex-rs/core/tests/suite/pending_input.rs index ad662d901b..1fcff4da3b 100644 --- a/codex-rs/core/tests/suite/pending_input.rs +++ b/codex-rs/core/tests/suite/pending_input.rs @@ -125,6 +125,11 @@ async fn injected_user_input_triggers_follow_up_request_with_deltas() { let _ = gate_completed_tx.send(()); + let _ = wait_for_event(&codex, |event| { + matches!(event, EventMsg::UserMessage(message) if message.message == "second prompt") + }) + .await; + wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; let requests = server.requests().await; From 4922b3e571d2da548f7e592707b4a1fac8fda09f Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 4 Feb 2026 21:38:39 +0000 Subject: [PATCH 29/90] feat: add phase 1 mem db (#10634) - Schema: thread_id (PK, FK to threads.id with cascade delete), trace_summary, memory_summary, updated_at. - Migration: creates the table and an index on (updated_at DESC, thread_id DESC) for efficient recent-first reads. - Runtime API (DB-only): - `get_thread_memory(thread_id)`: fetch one memory row. - `upsert_thread_memory(thread_id, trace_summary, memory_summary)`: insert/update by thread id and always advance updated_at. - `get_last_n_thread_memories_for_cwd(cwd, n)`: join thread_memory with threads and return newest n rows for an exact cwd match. - Model layer: introduced ThreadMemory and row conversion types to keep query decoding typed and consistent with existing state models. --- codex-rs/core/src/state_db.rs | 54 +++ .../state/migrations/0006_thread_memory.sql | 9 + codex-rs/state/src/lib.rs | 1 + codex-rs/state/src/model/mod.rs | 3 + codex-rs/state/src/model/thread_memory.rs | 52 +++ codex-rs/state/src/runtime.rs | 398 ++++++++++++++++++ 6 files changed, 517 insertions(+) create mode 100644 codex-rs/state/migrations/0006_thread_memory.sql create mode 100644 codex-rs/state/src/model/thread_memory.rs diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index 1cfb8f1566..c36d8afe4b 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -280,6 +280,60 @@ pub async fn persist_dynamic_tools( } } +/// Get memory summaries for a thread id using SQLite. +pub async fn get_thread_memory( + context: Option<&codex_state::StateRuntime>, + thread_id: ThreadId, + stage: &str, +) -> Option { + let ctx = context?; + match ctx.get_thread_memory(thread_id).await { + Ok(memory) => memory, + Err(err) => { + warn!("state db get_thread_memory failed during {stage}: {err}"); + None + } + } +} + +/// Upsert memory summaries for a thread id using SQLite. +pub async fn upsert_thread_memory( + context: Option<&codex_state::StateRuntime>, + thread_id: ThreadId, + trace_summary: &str, + memory_summary: &str, + stage: &str, +) -> Option { + let ctx = context?; + match ctx + .upsert_thread_memory(thread_id, trace_summary, memory_summary) + .await + { + Ok(memory) => Some(memory), + Err(err) => { + warn!("state db upsert_thread_memory failed during {stage}: {err}"); + None + } + } +} + +/// Get the last N memories corresponding to a cwd using an exact path match. +pub async fn get_last_n_thread_memories_for_cwd( + context: Option<&codex_state::StateRuntime>, + cwd: &Path, + n: usize, + stage: &str, +) -> Option> { + let ctx = context?; + match ctx.get_last_n_thread_memories_for_cwd(cwd, n).await { + Ok(memories) => Some(memories), + Err(err) => { + warn!("state db get_last_n_thread_memories_for_cwd failed during {stage}: {err}"); + None + } + } +} + /// Reconcile rollout items into SQLite, falling back to scanning the rollout file. pub async fn reconcile_rollout( context: Option<&codex_state::StateRuntime>, diff --git a/codex-rs/state/migrations/0006_thread_memory.sql b/codex-rs/state/migrations/0006_thread_memory.sql new file mode 100644 index 0000000000..fe90ab6679 --- /dev/null +++ b/codex-rs/state/migrations/0006_thread_memory.sql @@ -0,0 +1,9 @@ +CREATE TABLE thread_memory ( + thread_id TEXT PRIMARY KEY, + trace_summary TEXT NOT NULL, + memory_summary TEXT NOT NULL, + updated_at INTEGER NOT NULL, + FOREIGN KEY(thread_id) REFERENCES threads(id) ON DELETE CASCADE +); + +CREATE INDEX idx_thread_memory_updated_at ON thread_memory(updated_at DESC, thread_id DESC); diff --git a/codex-rs/state/src/lib.rs b/codex-rs/state/src/lib.rs index 2d37ecee96..7df3374096 100644 --- a/codex-rs/state/src/lib.rs +++ b/codex-rs/state/src/lib.rs @@ -25,6 +25,7 @@ pub use model::Anchor; pub use model::BackfillStats; pub use model::ExtractionOutcome; pub use model::SortKey; +pub use model::ThreadMemory; pub use model::ThreadMetadata; pub use model::ThreadMetadataBuilder; pub use model::ThreadsPage; diff --git a/codex-rs/state/src/model/mod.rs b/codex-rs/state/src/model/mod.rs index bd615d7561..d937019ec1 100644 --- a/codex-rs/state/src/model/mod.rs +++ b/codex-rs/state/src/model/mod.rs @@ -1,9 +1,11 @@ mod log; +mod thread_memory; mod thread_metadata; pub use log::LogEntry; pub use log::LogQuery; pub use log::LogRow; +pub use thread_memory::ThreadMemory; pub use thread_metadata::Anchor; pub use thread_metadata::BackfillStats; pub use thread_metadata::ExtractionOutcome; @@ -12,6 +14,7 @@ pub use thread_metadata::ThreadMetadata; pub use thread_metadata::ThreadMetadataBuilder; pub use thread_metadata::ThreadsPage; +pub(crate) use thread_memory::ThreadMemoryRow; pub(crate) use thread_metadata::ThreadRow; pub(crate) use thread_metadata::anchor_from_item; pub(crate) use thread_metadata::datetime_to_epoch_seconds; diff --git a/codex-rs/state/src/model/thread_memory.rs b/codex-rs/state/src/model/thread_memory.rs new file mode 100644 index 0000000000..6e3a34c21d --- /dev/null +++ b/codex-rs/state/src/model/thread_memory.rs @@ -0,0 +1,52 @@ +use anyhow::Result; +use chrono::DateTime; +use chrono::Utc; +use codex_protocol::ThreadId; +use sqlx::Row; +use sqlx::sqlite::SqliteRow; + +/// Stored memory summaries for a single thread. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ThreadMemory { + pub thread_id: ThreadId, + pub trace_summary: String, + pub memory_summary: String, + pub updated_at: DateTime, +} + +#[derive(Debug)] +pub(crate) struct ThreadMemoryRow { + thread_id: String, + trace_summary: String, + memory_summary: String, + updated_at: i64, +} + +impl ThreadMemoryRow { + pub(crate) fn try_from_row(row: &SqliteRow) -> Result { + Ok(Self { + thread_id: row.try_get("thread_id")?, + trace_summary: row.try_get("trace_summary")?, + memory_summary: row.try_get("memory_summary")?, + updated_at: row.try_get("updated_at")?, + }) + } +} + +impl TryFrom for ThreadMemory { + type Error = anyhow::Error; + + fn try_from(row: ThreadMemoryRow) -> std::result::Result { + Ok(Self { + thread_id: ThreadId::try_from(row.thread_id)?, + trace_summary: row.trace_summary, + memory_summary: row.memory_summary, + updated_at: epoch_seconds_to_datetime(row.updated_at)?, + }) + } +} + +fn epoch_seconds_to_datetime(secs: i64) -> Result> { + DateTime::::from_timestamp(secs, 0) + .ok_or_else(|| anyhow::anyhow!("invalid unix timestamp: {secs}")) +} diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index f4c2d76e8a..25c666fa6e 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -3,11 +3,13 @@ use crate::LogEntry; use crate::LogQuery; use crate::LogRow; use crate::SortKey; +use crate::ThreadMemory; use crate::ThreadMetadata; use crate::ThreadMetadataBuilder; use crate::ThreadsPage; use crate::apply_rollout_item; use crate::migrations::MIGRATOR; +use crate::model::ThreadMemoryRow; use crate::model::ThreadRow; use crate::model::anchor_from_item; use crate::model::datetime_to_epoch_seconds; @@ -153,6 +155,26 @@ ORDER BY position ASC Ok(Some(tools)) } + /// Get memory summaries for a thread, if present. + pub async fn get_thread_memory( + &self, + thread_id: ThreadId, + ) -> anyhow::Result> { + let row = sqlx::query( + r#" +SELECT thread_id, trace_summary, memory_summary, updated_at +FROM thread_memory +WHERE thread_id = ? + "#, + ) + .bind(thread_id.to_string()) + .fetch_optional(self.pool.as_ref()) + .await?; + + row.map(|row| ThreadMemoryRow::try_from_row(&row).and_then(ThreadMemory::try_from)) + .transpose() + } + /// Find a rollout path by thread id using the underlying database. pub async fn find_rollout_path_by_id( &self, @@ -405,6 +427,83 @@ ON CONFLICT(id) DO UPDATE SET Ok(()) } + /// Insert or update memory summaries for a thread. + /// + /// This method always advances `updated_at`, even if summaries are unchanged. + pub async fn upsert_thread_memory( + &self, + thread_id: ThreadId, + trace_summary: &str, + memory_summary: &str, + ) -> anyhow::Result { + if self.get_thread(thread_id).await?.is_none() { + return Err(anyhow::anyhow!("thread not found: {thread_id}")); + } + + let updated_at = Utc::now().timestamp(); + sqlx::query( + r#" +INSERT INTO thread_memory ( + thread_id, + trace_summary, + memory_summary, + updated_at +) VALUES (?, ?, ?, ?) +ON CONFLICT(thread_id) DO UPDATE SET + trace_summary = excluded.trace_summary, + memory_summary = excluded.memory_summary, + updated_at = CASE + WHEN excluded.updated_at <= thread_memory.updated_at THEN thread_memory.updated_at + 1 + ELSE excluded.updated_at + END + "#, + ) + .bind(thread_id.to_string()) + .bind(trace_summary) + .bind(memory_summary) + .bind(updated_at) + .execute(self.pool.as_ref()) + .await?; + + self.get_thread_memory(thread_id) + .await? + .ok_or_else(|| anyhow::anyhow!("failed to load upserted thread memory: {thread_id}")) + } + + /// Get the last `n` memories for threads with an exact cwd match. + pub async fn get_last_n_thread_memories_for_cwd( + &self, + cwd: &Path, + n: usize, + ) -> anyhow::Result> { + if n == 0 { + return Ok(Vec::new()); + } + + let rows = sqlx::query( + r#" +SELECT + m.thread_id, + m.trace_summary, + m.memory_summary, + m.updated_at +FROM thread_memory AS m +INNER JOIN threads AS t ON t.id = m.thread_id +WHERE t.cwd = ? +ORDER BY m.updated_at DESC, m.thread_id DESC +LIMIT ? + "#, + ) + .bind(cwd.display().to_string()) + .bind(n as i64) + .fetch_all(self.pool.as_ref()) + .await?; + + rows.into_iter() + .map(|row| ThreadMemoryRow::try_from_row(&row).and_then(ThreadMemory::try_from)) + .collect() + } + /// Persist dynamic tools for a thread if none have been stored yet. /// /// Dynamic tools are defined at thread start and should not change afterward. @@ -771,11 +870,20 @@ mod tests { use super::STATE_DB_FILENAME; use super::STATE_DB_VERSION; use super::StateRuntime; + use super::ThreadMetadata; use super::state_db_filename; + use chrono::DateTime; + use chrono::Utc; + use codex_protocol::ThreadId; + use codex_protocol::protocol::AskForApproval; + use codex_protocol::protocol::SandboxPolicy; use pretty_assertions::assert_eq; + use sqlx::Row; + use std::path::Path; use std::path::PathBuf; use std::time::SystemTime; use std::time::UNIX_EPOCH; + use uuid::Uuid; fn unique_temp_dir() -> PathBuf { let nanos = SystemTime::now() @@ -858,4 +966,294 @@ mod tests { let _ = tokio::fs::remove_dir_all(codex_home).await; } + + #[tokio::test] + async fn upsert_and_get_thread_memory() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.join("a")); + runtime + .upsert_thread(&metadata) + .await + .expect("upsert thread"); + + assert_eq!( + runtime + .get_thread_memory(thread_id) + .await + .expect("get memory before insert"), + None + ); + + let inserted = runtime + .upsert_thread_memory(thread_id, "trace one", "memory one") + .await + .expect("upsert memory"); + assert_eq!(inserted.thread_id, thread_id); + assert_eq!(inserted.trace_summary, "trace one"); + assert_eq!(inserted.memory_summary, "memory one"); + + let updated = runtime + .upsert_thread_memory(thread_id, "trace two", "memory two") + .await + .expect("update memory"); + assert_eq!(updated.thread_id, thread_id); + assert_eq!(updated.trace_summary, "trace two"); + assert_eq!(updated.memory_summary, "memory two"); + assert!( + updated.updated_at >= inserted.updated_at, + "updated_at should not move backward" + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn get_last_n_thread_memories_for_cwd_matches_exactly() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let cwd_a = codex_home.join("workspace-a"); + let cwd_b = codex_home.join("workspace-b"); + let t1 = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let t2 = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let t3 = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + runtime + .upsert_thread(&test_thread_metadata(&codex_home, t1, cwd_a.clone())) + .await + .expect("upsert thread t1"); + runtime + .upsert_thread(&test_thread_metadata(&codex_home, t2, cwd_a.clone())) + .await + .expect("upsert thread t2"); + runtime + .upsert_thread(&test_thread_metadata(&codex_home, t3, cwd_b.clone())) + .await + .expect("upsert thread t3"); + + let first = runtime + .upsert_thread_memory(t1, "trace-1", "memory-1") + .await + .expect("upsert t1 memory"); + runtime + .upsert_thread_memory(t2, "trace-2", "memory-2") + .await + .expect("upsert t2 memory"); + runtime + .upsert_thread_memory(t3, "trace-3", "memory-3") + .await + .expect("upsert t3 memory"); + // Ensure deterministic ordering even when updates happen in the same second. + runtime + .upsert_thread_memory(t1, "trace-1b", "memory-1b") + .await + .expect("upsert t1 memory again"); + + let cwd_a_memories = runtime + .get_last_n_thread_memories_for_cwd(cwd_a.as_path(), 2) + .await + .expect("list cwd a memories"); + assert_eq!(cwd_a_memories.len(), 2); + assert_eq!(cwd_a_memories[0].thread_id, t1); + assert_eq!(cwd_a_memories[0].trace_summary, "trace-1b"); + assert_eq!(cwd_a_memories[0].memory_summary, "memory-1b"); + assert_eq!(cwd_a_memories[1].thread_id, t2); + assert!(cwd_a_memories[0].updated_at >= first.updated_at); + + let cwd_b_memories = runtime + .get_last_n_thread_memories_for_cwd(cwd_b.as_path(), 10) + .await + .expect("list cwd b memories"); + assert_eq!(cwd_b_memories.len(), 1); + assert_eq!(cwd_b_memories[0].thread_id, t3); + + let none = runtime + .get_last_n_thread_memories_for_cwd(codex_home.join("missing").as_path(), 10) + .await + .expect("list missing cwd memories"); + assert_eq!(none, Vec::new()); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn upsert_thread_memory_errors_for_unknown_thread() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let unknown_thread_id = + ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let err = runtime + .upsert_thread_memory(unknown_thread_id, "trace", "memory") + .await + .expect_err("unknown thread should fail"); + assert!( + err.to_string().contains("thread not found"), + "error should mention missing thread: {err}" + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn get_last_n_thread_memories_for_cwd_zero_returns_empty() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let cwd = codex_home.join("workspace"); + runtime + .upsert_thread(&test_thread_metadata(&codex_home, thread_id, cwd.clone())) + .await + .expect("upsert thread"); + runtime + .upsert_thread_memory(thread_id, "trace", "memory") + .await + .expect("upsert memory"); + + let memories = runtime + .get_last_n_thread_memories_for_cwd(cwd.as_path(), 0) + .await + .expect("query memories"); + assert_eq!(memories, Vec::new()); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn get_last_n_thread_memories_for_cwd_does_not_prefix_match() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let cwd_exact = codex_home.join("workspace"); + let cwd_prefix = codex_home.join("workspace-child"); + let t_exact = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let t_prefix = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + runtime + .upsert_thread(&test_thread_metadata( + &codex_home, + t_exact, + cwd_exact.clone(), + )) + .await + .expect("upsert exact thread"); + runtime + .upsert_thread(&test_thread_metadata( + &codex_home, + t_prefix, + cwd_prefix.clone(), + )) + .await + .expect("upsert prefix thread"); + runtime + .upsert_thread_memory(t_exact, "trace-exact", "memory-exact") + .await + .expect("upsert exact memory"); + runtime + .upsert_thread_memory(t_prefix, "trace-prefix", "memory-prefix") + .await + .expect("upsert prefix memory"); + + let exact_only = runtime + .get_last_n_thread_memories_for_cwd(cwd_exact.as_path(), 10) + .await + .expect("query exact cwd"); + assert_eq!(exact_only.len(), 1); + assert_eq!(exact_only[0].thread_id, t_exact); + assert_eq!(exact_only[0].memory_summary, "memory-exact"); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn deleting_thread_cascades_thread_memory() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); + let cwd = codex_home.join("workspace"); + runtime + .upsert_thread(&test_thread_metadata(&codex_home, thread_id, cwd)) + .await + .expect("upsert thread"); + runtime + .upsert_thread_memory(thread_id, "trace", "memory") + .await + .expect("upsert memory"); + + let count_before = + sqlx::query("SELECT COUNT(*) AS count FROM thread_memory WHERE thread_id = ?") + .bind(thread_id.to_string()) + .fetch_one(runtime.pool.as_ref()) + .await + .expect("count before delete") + .try_get::("count") + .expect("count value"); + assert_eq!(count_before, 1); + + sqlx::query("DELETE FROM threads WHERE id = ?") + .bind(thread_id.to_string()) + .execute(runtime.pool.as_ref()) + .await + .expect("delete thread"); + + let count_after = + sqlx::query("SELECT COUNT(*) AS count FROM thread_memory WHERE thread_id = ?") + .bind(thread_id.to_string()) + .fetch_one(runtime.pool.as_ref()) + .await + .expect("count after delete") + .try_get::("count") + .expect("count value"); + assert_eq!(count_after, 0); + assert_eq!( + runtime + .get_thread_memory(thread_id) + .await + .expect("get memory after delete"), + None + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + fn test_thread_metadata( + codex_home: &Path, + thread_id: ThreadId, + cwd: PathBuf, + ) -> ThreadMetadata { + let now = DateTime::::from_timestamp(1_700_000_000, 0).expect("timestamp"); + ThreadMetadata { + id: thread_id, + rollout_path: codex_home.join(format!("rollout-{thread_id}.jsonl")), + created_at: now, + updated_at: now, + source: "cli".to_string(), + model_provider: "test-provider".to_string(), + cwd, + title: String::new(), + sandbox_policy: crate::extract::enum_to_string(&SandboxPolicy::ReadOnly), + approval_mode: crate::extract::enum_to_string(&AskForApproval::OnRequest), + tokens_used: 0, + has_user_event: true, + archived_at: None, + git_sha: None, + git_branch: None, + git_origin_url: None, + } + } } From d589ee05b169af83f97d9603bcdf8eada77732c9 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Wed, 4 Feb 2026 13:51:31 -0800 Subject: [PATCH 30/90] Fix jitter in TUI apps/connectors picker (#10593) This PR fixes jitter in the TUI apps menu by making the description column stable during rendering and height measurement. Added a `stable_desc_col` option to `SelectionViewParams`/`ListSelectionView`, introduced stable variants of the shared row render/measure helpers in `selection_popup_common`, and enabled the stable mode for the apps/connectors picker in `chatwidget`. With these changes, only the apps/connectors picker uses this new option, though it could be used elsewhere in the future. Why: previously, the description column was computed from only currently visible rows, so as you scrolled or filtered, the column could shift and cause wrapping/height changes that looked jumpy. Computing it from all rows in this popup keeps alignment and layout consistent as users scroll through avaialble apps. **Before:** https://github.com/user-attachments/assets/3856cb72-5465-4b90-a993-65a2ffb09113 **After:** https://github.com/user-attachments/assets/37b9d626-0b21-4c0f-8bb8-244c9ef971ff --- .../src/bottom_pane/list_selection_view.rs | 269 ++++++++++++++++-- codex-rs/tui/src/bottom_pane/mod.rs | 1 + .../src/bottom_pane/selection_popup_common.rs | 259 +++++++++++++++-- ...n_col_width_mode_auto_all_rows_scroll.snap | 31 ++ ...on_col_width_mode_auto_visible_scroll.snap | 31 ++ ...selection_col_width_mode_fixed_scroll.snap | 31 ++ codex-rs/tui/src/chatwidget.rs | 2 + 7 files changed, 577 insertions(+), 47 deletions(-) create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_all_rows_scroll.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_visible_scroll.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_fixed_scroll.snap diff --git a/codex-rs/tui/src/bottom_pane/list_selection_view.rs b/codex-rs/tui/src/bottom_pane/list_selection_view.rs index 095a095500..9bc046f010 100644 --- a/codex-rs/tui/src/bottom_pane/list_selection_view.rs +++ b/codex-rs/tui/src/bottom_pane/list_selection_view.rs @@ -23,14 +23,25 @@ use super::CancellationEvent; use super::bottom_pane_view::BottomPaneView; use super::popup_consts::MAX_POPUP_ROWS; use super::scroll_state::ScrollState; +pub(crate) use super::selection_popup_common::ColumnWidthMode; use super::selection_popup_common::GenericDisplayRow; use super::selection_popup_common::measure_rows_height; +use super::selection_popup_common::measure_rows_height_stable_col_widths; +use super::selection_popup_common::measure_rows_height_with_col_width_mode; use super::selection_popup_common::render_rows; +use super::selection_popup_common::render_rows_stable_col_widths; +use super::selection_popup_common::render_rows_with_col_width_mode; use unicode_width::UnicodeWidthStr; /// One selectable item in the generic selection list. pub(crate) type SelectionAction = Box; +/// One row in a [`ListSelectionView`] selection list. +/// +/// This is the source-of-truth model for row state before filtering and +/// formatting into render rows. A row is treated as disabled when either +/// `is_disabled` is true or `disabled_reason` is present; disabled rows cannot +/// be accepted and are skipped by keyboard navigation. #[derive(Default)] pub(crate) struct SelectionItem { pub name: String, @@ -46,6 +57,16 @@ pub(crate) struct SelectionItem { pub disabled_reason: Option, } +/// Construction-time configuration for [`ListSelectionView`]. +/// +/// This config is consumed once by [`ListSelectionView::new`]. After +/// construction, mutable interaction state (filtering, scrolling, and selected +/// row) lives on the view itself. +/// +/// `col_width_mode` controls column width mode in selection lists: +/// `AutoVisible` (default) measures only rows visible in the viewport +/// `AutoAllRows` measures all rows to ensure stable column widths as the user scrolls +/// `Fixed` used a fixed 30/70 split between columns pub(crate) struct SelectionViewParams { pub title: Option, pub subtitle: Option, @@ -54,6 +75,7 @@ pub(crate) struct SelectionViewParams { pub items: Vec, pub is_searchable: bool, pub search_placeholder: Option, + pub col_width_mode: ColumnWidthMode, pub header: Box, pub initial_selected_idx: Option, } @@ -68,12 +90,18 @@ impl Default for SelectionViewParams { items: Vec::new(), is_searchable: false, search_placeholder: None, + col_width_mode: ColumnWidthMode::AutoVisible, header: Box::new(()), initial_selected_idx: None, } } } +/// Runtime state for rendering and interacting with a list-based selection popup. +/// +/// This type is the single authority for filtered index mapping between +/// visible rows and source items and for preserving selection while filters +/// change. pub(crate) struct ListSelectionView { footer_note: Option>, footer_hint: Option>, @@ -84,6 +112,7 @@ pub(crate) struct ListSelectionView { is_searchable: bool, search_query: String, search_placeholder: Option, + col_width_mode: ColumnWidthMode, filtered_indices: Vec, last_selected_actual_idx: Option, header: Box, @@ -91,6 +120,13 @@ pub(crate) struct ListSelectionView { } impl ListSelectionView { + /// Create a selection popup view with filtering, scrolling, and callbacks wired. + /// + /// The constructor normalizes header/title composition and immediately + /// applies filtering so `ScrollState` starts in a valid visible range. + /// When search is enabled, rows without `search_value` will disappear as + /// soon as the query is non-empty, which can look like dropped data unless + /// callers intentionally populate that field. pub fn new(params: SelectionViewParams, app_event_tx: AppEventSender) -> Self { let mut header = params.header; if params.title.is_some() || params.subtitle.is_some() { @@ -116,6 +152,7 @@ impl ListSelectionView { } else { None }, + col_width_mode: params.col_width_mode, filtered_indices: Vec::new(), last_selected_actual_idx: None, header, @@ -434,12 +471,27 @@ impl Renderable for ListSelectionView { // Build the same display rows used by the renderer so wrapping math matches. let rows = self.build_rows(); let rows_width = Self::rows_width(width); - let rows_height = measure_rows_height( - &rows, - &self.state, - MAX_POPUP_ROWS, - rows_width.saturating_add(1), - ); + let rows_height = match self.col_width_mode { + ColumnWidthMode::AutoVisible => measure_rows_height( + &rows, + &self.state, + MAX_POPUP_ROWS, + rows_width.saturating_add(1), + ), + ColumnWidthMode::AutoAllRows => measure_rows_height_stable_col_widths( + &rows, + &self.state, + MAX_POPUP_ROWS, + rows_width.saturating_add(1), + ), + ColumnWidthMode::Fixed => measure_rows_height_with_col_width_mode( + &rows, + &self.state, + MAX_POPUP_ROWS, + rows_width.saturating_add(1), + ColumnWidthMode::Fixed, + ), + }; // Subtract 4 for the padding on the left and right of the header. let mut height = self.header.desired_height(width.saturating_sub(4)); @@ -483,12 +535,27 @@ impl Renderable for ListSelectionView { .desired_height(outer_content_area.width.saturating_sub(4)); let rows = self.build_rows(); let rows_width = Self::rows_width(outer_content_area.width); - let rows_height = measure_rows_height( - &rows, - &self.state, - MAX_POPUP_ROWS, - rows_width.saturating_add(1), - ); + let rows_height = match self.col_width_mode { + ColumnWidthMode::AutoVisible => measure_rows_height( + &rows, + &self.state, + MAX_POPUP_ROWS, + rows_width.saturating_add(1), + ), + ColumnWidthMode::AutoAllRows => measure_rows_height_stable_col_widths( + &rows, + &self.state, + MAX_POPUP_ROWS, + rows_width.saturating_add(1), + ), + ColumnWidthMode::Fixed => measure_rows_height_with_col_width_mode( + &rows, + &self.state, + MAX_POPUP_ROWS, + rows_width.saturating_add(1), + ColumnWidthMode::Fixed, + ), + }; let [header_area, _, search_area, list_area] = Layout::vertical([ Constraint::Max(header_height), Constraint::Max(1), @@ -529,14 +596,33 @@ impl Renderable for ListSelectionView { width: rows_width.max(1), height: list_area.height, }; - render_rows( - render_area, - buf, - &rows, - &self.state, - render_area.height as usize, - "no matches", - ); + match self.col_width_mode { + ColumnWidthMode::AutoVisible => render_rows( + render_area, + buf, + &rows, + &self.state, + render_area.height as usize, + "no matches", + ), + ColumnWidthMode::AutoAllRows => render_rows_stable_col_widths( + render_area, + buf, + &rows, + &self.state, + render_area.height as usize, + "no matches", + ), + ColumnWidthMode::Fixed => render_rows_with_col_width_mode( + render_area, + buf, + &rows, + &self.state, + render_area.height as usize, + "no matches", + ColumnWidthMode::Fixed, + ), + } } if footer_area.height > 0 { @@ -585,7 +671,9 @@ mod tests { use super::*; use crate::app_event::AppEvent; use crate::bottom_pane::popup_consts::standard_popup_hint_line; + use crossterm::event::KeyCode; use insta::assert_snapshot; + use pretty_assertions::assert_eq; use ratatui::layout::Rect; use tokio::sync::mpsc::unbounded_channel; @@ -647,6 +735,55 @@ mod tests { lines.join("\n") } + fn description_col(rendered: &str, item_marker: &str, description: &str) -> usize { + let line = rendered + .lines() + .find(|line| line.contains(item_marker) && line.contains(description)) + .expect("expected rendered line to contain row marker and description"); + line.find(description) + .expect("expected rendered line to contain description") + } + + fn make_scrolling_width_items() -> Vec { + let mut items: Vec = (1..=8) + .map(|idx| SelectionItem { + name: format!("Item {idx}"), + description: Some(format!("desc {idx}")), + dismiss_on_select: true, + ..Default::default() + }) + .collect(); + items.push(SelectionItem { + name: "Item 9 with an intentionally much longer name".to_string(), + description: Some("desc 9".to_string()), + dismiss_on_select: true, + ..Default::default() + }); + items + } + + fn render_before_after_scroll_snapshot(col_width_mode: ColumnWidthMode, width: u16) -> String { + let (tx_raw, _rx) = unbounded_channel::(); + let tx = AppEventSender::new(tx_raw); + let mut view = ListSelectionView::new( + SelectionViewParams { + title: Some("Debug".to_string()), + items: make_scrolling_width_items(), + col_width_mode, + ..Default::default() + }, + tx, + ); + + let before_scroll = render_lines_with_width(&view, width); + for _ in 0..8 { + view.handle_key_event(KeyEvent::from(KeyCode::Down)); + } + let after_scroll = render_lines_with_width(&view, width); + + format!("before scroll:\n{before_scroll}\n\nafter scroll:\n{after_scroll}") + } + #[test] fn renders_blank_line_between_title_and_items_without_subtitle() { let view = make_selection_view(None); @@ -921,4 +1058,96 @@ mod tests { render_lines_with_width(&view, 24) ); } + + #[test] + fn snapshot_auto_visible_col_width_mode_scroll_behavior() { + assert_snapshot!( + "list_selection_col_width_mode_auto_visible_scroll", + render_before_after_scroll_snapshot(ColumnWidthMode::AutoVisible, 96) + ); + } + + #[test] + fn snapshot_auto_all_rows_col_width_mode_scroll_behavior() { + assert_snapshot!( + "list_selection_col_width_mode_auto_all_rows_scroll", + render_before_after_scroll_snapshot(ColumnWidthMode::AutoAllRows, 96) + ); + } + + #[test] + fn snapshot_fixed_col_width_mode_scroll_behavior() { + assert_snapshot!( + "list_selection_col_width_mode_fixed_scroll", + render_before_after_scroll_snapshot(ColumnWidthMode::Fixed, 96) + ); + } + + #[test] + fn auto_all_rows_col_width_does_not_shift_when_scrolling() { + let (tx_raw, _rx) = unbounded_channel::(); + let tx = AppEventSender::new(tx_raw); + + let mut view = ListSelectionView::new( + SelectionViewParams { + title: Some("Debug".to_string()), + items: make_scrolling_width_items(), + col_width_mode: ColumnWidthMode::AutoAllRows, + ..Default::default() + }, + tx, + ); + + let before_scroll = render_lines_with_width(&view, 96); + for _ in 0..8 { + view.handle_key_event(KeyEvent::from(KeyCode::Down)); + } + let after_scroll = render_lines_with_width(&view, 96); + + assert!( + after_scroll.contains("9. Item 9 with an intentionally much longer name"), + "expected the scrolled view to include the longer row:\n{after_scroll}" + ); + + let before_col = description_col(&before_scroll, "8. Item 8", "desc 8"); + let after_col = description_col(&after_scroll, "8. Item 8", "desc 8"); + assert_eq!( + before_col, after_col, + "description column changed across scroll:\nbefore:\n{before_scroll}\nafter:\n{after_scroll}" + ); + } + + #[test] + fn fixed_col_width_is_30_70_and_does_not_shift_when_scrolling() { + let (tx_raw, _rx) = unbounded_channel::(); + let tx = AppEventSender::new(tx_raw); + let width = 96; + let mut view = ListSelectionView::new( + SelectionViewParams { + title: Some("Debug".to_string()), + items: make_scrolling_width_items(), + col_width_mode: ColumnWidthMode::Fixed, + ..Default::default() + }, + tx, + ); + + let before_scroll = render_lines_with_width(&view, width); + let before_col = description_col(&before_scroll, "8. Item 8", "desc 8"); + let expected_desc_col = ((width.saturating_sub(2) as usize) * 3) / 10; + assert_eq!( + before_col, expected_desc_col, + "fixed mode should place description column at a 30/70 split:\n{before_scroll}" + ); + + for _ in 0..8 { + view.handle_key_event(KeyEvent::from(KeyCode::Down)); + } + let after_scroll = render_lines_with_width(&view, width); + let after_col = description_col(&after_scroll, "8. Item 8", "desc 8"); + assert_eq!( + before_col, after_col, + "fixed description column changed across scroll:\nbefore:\n{before_scroll}\nafter:\n{after_scroll}" + ); + } } diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index cd5e2adb80..b251beb80e 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -65,6 +65,7 @@ mod skill_popup; mod skills_toggle_view; mod slash_commands; pub(crate) use footer::CollaborationModeIndicator; +pub(crate) use list_selection_view::ColumnWidthMode; pub(crate) use list_selection_view::SelectionViewParams; mod feedback_view; pub(crate) use feedback_view::FeedbackAudience; diff --git a/codex-rs/tui/src/bottom_pane/selection_popup_common.rs b/codex-rs/tui/src/bottom_pane/selection_popup_common.rs index 89ef8b3b3f..f8e1ad8902 100644 --- a/codex-rs/tui/src/bottom_pane/selection_popup_common.rs +++ b/codex-rs/tui/src/bottom_pane/selection_popup_common.rs @@ -19,7 +19,11 @@ use crate::style::user_message_style; use super::scroll_state::ScrollState; -/// A generic representation of a display row for selection popups. +/// Render-ready representation of one row in a selection popup. +/// +/// This type contains presentation-focused fields that are intentionally more +/// concrete than source domain models. `match_indices` are character offsets +/// into `name`, and `wrap_indent` is interpreted in terminal cell columns. #[derive(Default)] pub(crate) struct GenericDisplayRow { pub name: String, @@ -31,6 +35,25 @@ pub(crate) struct GenericDisplayRow { pub wrap_indent: Option, // optional indent for wrapped lines } +/// Controls how selection rows choose the split between left/right name/description columns. +/// +/// Callers should use the same mode for both measurement and rendering, or the +/// popup can reserve the wrong number of lines and clip content. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(not(test), allow(dead_code))] +pub(crate) enum ColumnWidthMode { + /// Derive column placement from only the visible viewport rows. + #[default] + AutoVisible, + /// Derive column placement from all rows so scrolling does not shift columns. + AutoAllRows, + /// Use a fixed two-column split: 30% left (name), 70% right (description). + Fixed, +} + +const FIXED_LEFT_COLUMN_NUMERATOR: usize = 3; +const FIXED_LEFT_COLUMN_DENOMINATOR: usize = 10; + const MENU_SURFACE_INSET_V: u16 = 1; const MENU_SURFACE_INSET_H: u16 = 2; @@ -50,7 +73,8 @@ pub(crate) const fn menu_surface_padding_height() -> u16 { /// Paint the shared menu background and return the inset content area. /// /// This keeps the surface treatment consistent across selection-style overlays -/// (for example `/model`, approvals, and request-user-input). +/// (for example `/model`, approvals, and request-user-input). Callers should +/// render all inner content in the returned rect, not the original area. pub(crate) fn render_menu_surface(area: Rect, buf: &mut Buffer) -> Rect { if area.is_empty() { return area; @@ -61,6 +85,10 @@ pub(crate) fn render_menu_surface(area: Rect, buf: &mut Buffer) -> Rect { menu_surface_inset(area) } +/// Wrap a styled line while preserving span styles. +/// +/// The function clamps `width` to at least one terminal cell so callers can use +/// it safely with narrow layouts. pub(crate) fn wrap_styled_line<'a>(line: &'a Line<'a>, width: u16) -> Vec> { use crate::wrapping::RtOptions; use crate::wrapping::word_wrap_line; @@ -143,34 +171,60 @@ fn truncate_line_with_ellipsis_if_overflow(line: Line<'static>, max_width: usize Line::from(spans) } -/// Compute a shared description-column start based on the widest visible name -/// plus two spaces of padding. Ensures at least one column is left for the -/// description. +/// Computes the shared start column used for descriptions in selection rows. +/// The column is derived from the widest row name plus two spaces of padding +/// while always leaving at least one terminal cell for description content. +/// [`ColumnWidthMode::AutoAllRows`] computes width across the full dataset so +/// the description column does not shift as the user scrolls. fn compute_desc_col( rows_all: &[GenericDisplayRow], start_idx: usize, visible_items: usize, content_width: u16, + col_width_mode: ColumnWidthMode, ) -> usize { - let visible_range = start_idx..(start_idx + visible_items); - let max_name_width = rows_all - .iter() - .enumerate() - .filter(|(i, _)| visible_range.contains(i)) - .map(|(_, r)| { - let mut spans: Vec = vec![r.name.clone().into()]; - if r.disabled_reason.is_some() { - spans.push(" (disabled)".dim()); - } - Line::from(spans).width() - }) - .max() - .unwrap_or(0); - let mut desc_col = max_name_width.saturating_add(2); - if (desc_col as u16) >= content_width { - desc_col = content_width.saturating_sub(1) as usize; + if content_width <= 1 { + return 0; + } + + let max_desc_col = content_width.saturating_sub(1) as usize; + match col_width_mode { + ColumnWidthMode::Fixed => ((content_width as usize * FIXED_LEFT_COLUMN_NUMERATOR) + / FIXED_LEFT_COLUMN_DENOMINATOR) + .clamp(1, max_desc_col), + ColumnWidthMode::AutoVisible | ColumnWidthMode::AutoAllRows => { + let max_name_width = match col_width_mode { + ColumnWidthMode::AutoVisible => rows_all + .iter() + .enumerate() + .skip(start_idx) + .take(visible_items) + .map(|(_, row)| { + let mut spans: Vec = vec![row.name.clone().into()]; + if row.disabled_reason.is_some() { + spans.push(" (disabled)".dim()); + } + Line::from(spans).width() + }) + .max() + .unwrap_or(0), + ColumnWidthMode::AutoAllRows => rows_all + .iter() + .map(|row| { + let mut spans: Vec = vec![row.name.clone().into()]; + if row.disabled_reason.is_some() { + spans.push(" (disabled)".dim()); + } + Line::from(spans).width() + }) + .max() + .unwrap_or(0), + ColumnWidthMode::Fixed => 0, + }; + + max_name_width.saturating_add(2).min(max_desc_col) + } } - desc_col } /// Determine how many spaces to indent wrapped lines for a row. @@ -268,13 +322,14 @@ fn build_full_line(row: &GenericDisplayRow, desc_col: usize) -> Line<'static> { /// Render a list of rows using the provided ScrollState, with shared styling /// and behavior for selection popups. -pub(crate) fn render_rows( +fn render_rows_inner( area: Rect, buf: &mut Buffer, rows_all: &[GenericDisplayRow], state: &ScrollState, max_results: usize, empty_message: &str, + col_width_mode: ColumnWidthMode, ) { if rows_all.is_empty() { if area.height > 0 { @@ -301,7 +356,13 @@ pub(crate) fn render_rows( } } - let desc_col = compute_desc_col(rows_all, start_idx, visible_items, area.width); + let desc_col = compute_desc_col( + rows_all, + start_idx, + visible_items, + area.width, + col_width_mode, + ); // Render items, wrapping descriptions and aligning wrapped lines under the // shared description column. Stop when we run out of vertical space. @@ -358,7 +419,88 @@ pub(crate) fn render_rows( } } +/// Render a list of rows using the provided ScrollState, with shared styling +/// and behavior for selection popups. +/// Description alignment is computed from visible rows only, which allows the +/// layout to adapt tightly to the current viewport. +/// +/// This function should be paired with [`measure_rows_height`] when reserving +/// space; pairing it with a different measurement mode can cause clipping. +pub(crate) fn render_rows( + area: Rect, + buf: &mut Buffer, + rows_all: &[GenericDisplayRow], + state: &ScrollState, + max_results: usize, + empty_message: &str, +) { + render_rows_inner( + area, + buf, + rows_all, + state, + max_results, + empty_message, + ColumnWidthMode::AutoVisible, + ); +} + +/// Render a list of rows using the provided ScrollState, with shared styling +/// and behavior for selection popups. +/// This mode keeps column placement stable while scrolling by sizing the +/// description column against the full dataset. +/// +/// This function should be paired with +/// [`measure_rows_height_stable_col_widths`] so reserved and rendered heights +/// stay in sync. +pub(crate) fn render_rows_stable_col_widths( + area: Rect, + buf: &mut Buffer, + rows_all: &[GenericDisplayRow], + state: &ScrollState, + max_results: usize, + empty_message: &str, +) { + render_rows_inner( + area, + buf, + rows_all, + state, + max_results, + empty_message, + ColumnWidthMode::AutoAllRows, + ); +} + +/// Render a list of rows using the provided ScrollState and explicit +/// [`ColumnWidthMode`] behavior. +/// +/// This is the low-level entry point for callers that need to thread a mode +/// through higher-level configuration. +pub(crate) fn render_rows_with_col_width_mode( + area: Rect, + buf: &mut Buffer, + rows_all: &[GenericDisplayRow], + state: &ScrollState, + max_results: usize, + empty_message: &str, + col_width_mode: ColumnWidthMode, +) { + render_rows_inner( + area, + buf, + rows_all, + state, + max_results, + empty_message, + col_width_mode, + ); +} + /// Render rows as a single line each (no wrapping), truncating overflow with an ellipsis. +/// +/// This path always uses viewport-local width alignment and is best for dense +/// list UIs where multi-line descriptions would add too much vertical churn. pub(crate) fn render_rows_single_line( area: Rect, buf: &mut Buffer, @@ -390,7 +532,13 @@ pub(crate) fn render_rows_single_line( } } - let desc_col = compute_desc_col(rows_all, start_idx, visible_items, area.width); + let desc_col = compute_desc_col( + rows_all, + start_idx, + visible_items, + area.width, + ColumnWidthMode::AutoVisible, + ); let mut cur_y = area.y; for (i, row) in rows_all @@ -433,11 +581,62 @@ pub(crate) fn render_rows_single_line( /// items from `rows_all` given the current scroll/selection state and the /// available `width`. Accounts for description wrapping and alignment so the /// caller can allocate sufficient vertical space. +/// +/// This function matches [`render_rows`] semantics (`AutoVisible` column +/// sizing). Mixing it with stable or fixed render modes can under- or +/// over-estimate required height. pub(crate) fn measure_rows_height( rows_all: &[GenericDisplayRow], state: &ScrollState, max_results: usize, width: u16, +) -> u16 { + measure_rows_height_inner( + rows_all, + state, + max_results, + width, + ColumnWidthMode::AutoVisible, + ) +} + +/// Measures selection-row height while using full-dataset column alignment. +/// This should be paired with [`render_rows_stable_col_widths`] so layout +/// reservation matches rendering behavior. +pub(crate) fn measure_rows_height_stable_col_widths( + rows_all: &[GenericDisplayRow], + state: &ScrollState, + max_results: usize, + width: u16, +) -> u16 { + measure_rows_height_inner( + rows_all, + state, + max_results, + width, + ColumnWidthMode::AutoAllRows, + ) +} + +/// Measure selection-row height using explicit [`ColumnWidthMode`] behavior. +/// +/// This is the low-level companion to [`render_rows_with_col_width_mode`]. +pub(crate) fn measure_rows_height_with_col_width_mode( + rows_all: &[GenericDisplayRow], + state: &ScrollState, + max_results: usize, + width: u16, + col_width_mode: ColumnWidthMode, +) -> u16 { + measure_rows_height_inner(rows_all, state, max_results, width, col_width_mode) +} + +fn measure_rows_height_inner( + rows_all: &[GenericDisplayRow], + state: &ScrollState, + max_results: usize, + width: u16, + col_width_mode: ColumnWidthMode, ) -> u16 { if rows_all.is_empty() { return 1; // placeholder "no matches" line @@ -458,7 +657,13 @@ pub(crate) fn measure_rows_height( } } - let desc_col = compute_desc_col(rows_all, start_idx, visible_items, content_width); + let desc_col = compute_desc_col( + rows_all, + start_idx, + visible_items, + content_width, + col_width_mode, + ); use crate::wrapping::RtOptions; use crate::wrapping::word_wrap_line; diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_all_rows_scroll.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_all_rows_scroll.snap new file mode 100644 index 0000000000..6aaf439a9f --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_all_rows_scroll.snap @@ -0,0 +1,31 @@ +--- +source: tui/src/bottom_pane/list_selection_view.rs +assertion_line: 1054 +expression: "render_before_after_scroll_snapshot(ColumnWidthMode::AutoAllRows, 96)" +--- +before scroll: + + Debug + +› 1. Item 1 desc 1 + 2. Item 2 desc 2 + 3. Item 3 desc 3 + 4. Item 4 desc 4 + 5. Item 5 desc 5 + 6. Item 6 desc 6 + 7. Item 7 desc 7 + 8. Item 8 desc 8 + + +after scroll: + + Debug + + 2. Item 2 desc 2 + 3. Item 3 desc 3 + 4. Item 4 desc 4 + 5. Item 5 desc 5 + 6. Item 6 desc 6 + 7. Item 7 desc 7 + 8. Item 8 desc 8 +› 9. Item 9 with an intentionally much longer name desc 9 diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_visible_scroll.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_visible_scroll.snap new file mode 100644 index 0000000000..6875fb5433 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_auto_visible_scroll.snap @@ -0,0 +1,31 @@ +--- +source: tui/src/bottom_pane/list_selection_view.rs +assertion_line: 1046 +expression: "render_before_after_scroll_snapshot(ColumnWidthMode::AutoVisible, 96)" +--- +before scroll: + + Debug + +› 1. Item 1 desc 1 + 2. Item 2 desc 2 + 3. Item 3 desc 3 + 4. Item 4 desc 4 + 5. Item 5 desc 5 + 6. Item 6 desc 6 + 7. Item 7 desc 7 + 8. Item 8 desc 8 + + +after scroll: + + Debug + + 2. Item 2 desc 2 + 3. Item 3 desc 3 + 4. Item 4 desc 4 + 5. Item 5 desc 5 + 6. Item 6 desc 6 + 7. Item 7 desc 7 + 8. Item 8 desc 8 +› 9. Item 9 with an intentionally much longer name desc 9 diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_fixed_scroll.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_fixed_scroll.snap new file mode 100644 index 0000000000..4672ab7f27 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__list_selection_view__tests__list_selection_col_width_mode_fixed_scroll.snap @@ -0,0 +1,31 @@ +--- +source: tui/src/bottom_pane/list_selection_view.rs +assertion_line: 1062 +expression: "render_before_after_scroll_snapshot(ColumnWidthMode::Fixed, 96)" +--- +before scroll: + + Debug + +› 1. Item 1 desc 1 + 2. Item 2 desc 2 + 3. Item 3 desc 3 + 4. Item 4 desc 4 + 5. Item 5 desc 5 + 6. Item 6 desc 6 + 7. Item 7 desc 7 + 8. Item 8 desc 8 + + +after scroll: + + Debug + + 2. Item 2 desc 2 + 3. Item 3 desc 3 + 4. Item 4 desc 4 + 5. Item 5 desc 5 + 6. Item 6 desc 6 + 7. Item 7 desc 7 + 8. Item 8 desc 8 +› 9. Item 9 with an intent… desc 9 diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 52a1b54404..58ffcc37f0 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -144,6 +144,7 @@ use crate::bottom_pane::BottomPane; use crate::bottom_pane::BottomPaneParams; use crate::bottom_pane::CancellationEvent; use crate::bottom_pane::CollaborationModeIndicator; +use crate::bottom_pane::ColumnWidthMode; use crate::bottom_pane::DOUBLE_PRESS_QUIT_SHORTCUT_ENABLED; use crate::bottom_pane::ExperimentalFeatureItem; use crate::bottom_pane::ExperimentalFeaturesView; @@ -5742,6 +5743,7 @@ impl ChatWidget { items, is_searchable: true, search_placeholder: Some("Type to search apps".to_string()), + col_width_mode: ColumnWidthMode::AutoAllRows, ..Default::default() }); } From acdbd8edc508c19f6bfe58d464eeb1f459fb7585 Mon Sep 17 00:00:00 2001 From: Matthew Zeng Date: Wed, 4 Feb 2026 13:51:57 -0800 Subject: [PATCH 31/90] [apps] Cache MCP actions from apps. (#10662) MCP actions take a long time to load for users with lots of apps installed. Adding a cache for these actions with 1hr expiration, given that they are almost always aren't going to change unless people install another app, which means they also need to restart codex to pick it up. --- codex-rs/core/src/mcp_connection_manager.rs | 83 +++++++++++++++++++-- 1 file changed, 78 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 456114c30f..d6447738c2 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -12,7 +12,10 @@ use std::env; use std::ffi::OsString; use std::path::PathBuf; use std::sync::Arc; +use std::sync::LazyLock; +use std::sync::Mutex as StdMutex; use std::time::Duration; +use std::time::Instant; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; use crate::mcp::auth::McpAuthStatusEntry; @@ -83,6 +86,8 @@ pub const DEFAULT_STARTUP_TIMEOUT: Duration = Duration::from_secs(10); /// Default timeout for individual tool calls. const DEFAULT_TOOL_TIMEOUT: Duration = Duration::from_secs(60); +const CODEX_APPS_TOOLS_CACHE_TTL: Duration = Duration::from_secs(3600); + /// The Responses API requires tool names to match `^[a-zA-Z0-9_-]+$`. /// MCP server/tool names are user-controlled, so sanitize the fully-qualified /// name we expose to the model by replacing any disallowed character with `_`. @@ -161,6 +166,15 @@ pub(crate) struct ToolInfo { pub(crate) connector_name: Option, } +#[derive(Clone)] +struct CachedCodexAppsTools { + expires_at: Instant, + tools: Vec, +} + +static CODEX_APPS_TOOLS_CACHE: LazyLock>> = + LazyLock::new(|| StdMutex::new(None)); + type ResponderMap = HashMap<(String, RequestId), oneshot::Sender>; #[derive(Clone, Default)] @@ -465,13 +479,28 @@ impl McpConnectionManager { #[instrument(level = "trace", skip_all)] pub async fn list_all_tools(&self) -> HashMap { let mut tools = HashMap::new(); - for managed_client in self.clients.values() { + for (server_name, managed_client) in &self.clients { let client = managed_client.client().await.ok(); if let Some(client) = client { - tools.extend(qualify_tools(filter_tools( - client.tools, - client.tool_filter, - ))); + let rmcp_client = client.client; + let tool_timeout = client.tool_timeout; + let tool_filter = client.tool_filter; + let mut server_tools = client.tools; + + if server_name == CODEX_APPS_MCP_SERVER_NAME { + match list_tools_for_client(server_name, &rmcp_client, tool_timeout).await { + Ok(fresh_or_cached_tools) => { + server_tools = fresh_or_cached_tools; + } + Err(err) => { + warn!( + "Failed to refresh tools for MCP server '{server_name}', using startup snapshot: {err:#}" + ); + } + } + } + + tools.extend(qualify_tools(filter_tools(server_tools, tool_filter))); } } tools @@ -965,6 +994,50 @@ async fn list_tools_for_client( server_name: &str, client: &Arc, timeout: Option, +) -> Result> { + if server_name == CODEX_APPS_MCP_SERVER_NAME + && let Some(cached_tools) = read_cached_codex_apps_tools() + { + return Ok(cached_tools); + } + + let tools = list_tools_for_client_uncached(server_name, client, timeout).await?; + if server_name == CODEX_APPS_MCP_SERVER_NAME { + write_cached_codex_apps_tools(&tools); + } + Ok(tools) +} + +fn read_cached_codex_apps_tools() -> Option> { + let mut cache_guard = CODEX_APPS_TOOLS_CACHE + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let now = Instant::now(); + + if let Some(cached) = cache_guard.as_ref() + && now < cached.expires_at + { + return Some(cached.tools.clone()); + } + + *cache_guard = None; + None +} + +fn write_cached_codex_apps_tools(tools: &[ToolInfo]) { + let mut cache_guard = CODEX_APPS_TOOLS_CACHE + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + *cache_guard = Some(CachedCodexAppsTools { + expires_at: Instant::now() + CODEX_APPS_TOOLS_CACHE_TTL, + tools: tools.to_vec(), + }); +} + +async fn list_tools_for_client_uncached( + server_name: &str, + client: &Arc, + timeout: Option, ) -> Result> { let resp = client.list_tools_with_connector_ids(None, timeout).await?; Ok(resp From 7c6d21a414ba5cd21e54c8807c8b592f8de3bdcb Mon Sep 17 00:00:00 2001 From: gt-oai Date: Wed, 4 Feb 2026 22:19:06 +0000 Subject: [PATCH 32/90] Fix test_shell_command_interruption flake (#10649) ## Human summary Sandboxing (specifically `LandlockRestrict`) is means that e.g. `sleep 10` fails immediately. Therefore it cannot be interrupted. In suite::interrupt::test_shell_command_interruption, sleep 10 is issued at 17:28:16.554 (ToolCall: shell_command {"command":"sleep 10"...}), then fails at 17:28:16.589 with duration_ms=34, success=false, exit_code=101, and Sandbox(LandlockRestrict). ## Codex summary - set `sandbox_mode = "danger-full-access"` in `interrupt` and `v2/turn_interrupt` integration tests - set `sandbox: Some(SandboxMode::DangerFullAccess)` in `test_codex_jsonrpc_conversation_flow` - set `sandbox_policy: Some(SandboxPolicy::DangerFullAccess)` in `command_execution_notifications_include_process_id` ## Why On some Linux CI environments, command execution fails immediately with `LandlockRestrict` when sandboxed. These tests are intended to validate JSON-RPC/task lifecycle behavior (interrupt semantics, command notification shape/process id, request flow), but early sandbox startup failure changes turn flow and can trigger extra follow-up requests, causing flakes. This change removes environment-specific sandbox startup dependency from these tests while preserving their primary intent. ## Testing - not run in this environment (per request) --- .../app-server/tests/suite/codex_message_processor_flow.rs | 3 ++- codex-rs/app-server/tests/suite/interrupt.rs | 2 +- codex-rs/app-server/tests/suite/v2/turn_interrupt.rs | 2 +- codex-rs/app-server/tests/suite/v2/turn_start.rs | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs index d905c3c1b5..a041b8b0ba 100644 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs @@ -36,7 +36,7 @@ use std::path::Path; use tempfile::TempDir; use tokio::time::timeout; -const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(20); #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_codex_jsonrpc_conversation_flow() -> Result<()> { @@ -76,6 +76,7 @@ async fn test_codex_jsonrpc_conversation_flow() -> Result<()> { let new_conv_id = mcp .send_new_conversation_request(NewConversationParams { cwd: Some(working_directory.to_string_lossy().into_owned()), + sandbox: Some(SandboxMode::DangerFullAccess), ..Default::default() }) .await?; diff --git a/codex-rs/app-server/tests/suite/interrupt.rs b/codex-rs/app-server/tests/suite/interrupt.rs index f8dc2a7e8e..2270afce28 100644 --- a/codex-rs/app-server/tests/suite/interrupt.rs +++ b/codex-rs/app-server/tests/suite/interrupt.rs @@ -147,7 +147,7 @@ fn create_config_toml(codex_home: &Path, server_uri: String) -> std::io::Result< r#" model = "mock-model" approval_policy = "never" -sandbox_mode = "read-only" +sandbox_mode = "danger-full-access" model_provider = "mock_provider" diff --git a/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs b/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs index 9c804aa666..486e915f6f 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_interrupt.rs @@ -129,7 +129,7 @@ fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io r#" model = "mock-model" approval_policy = "never" -sandbox_mode = "workspace-write" +sandbox_mode = "danger-full-access" model_provider = "mock_provider" diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 67a147c39e..2fc68e0beb 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -1652,6 +1652,7 @@ async fn command_execution_notifications_include_process_id() -> Result<()> { text: "run a command".to_string(), text_elements: Vec::new(), }], + sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::DangerFullAccess), ..Default::default() }) .await?; From d452bb3ae5b5e0f715bba3a44d7d30a51b5f28ae Mon Sep 17 00:00:00 2001 From: gt-oai Date: Wed, 4 Feb 2026 22:26:17 +0000 Subject: [PATCH 33/90] Add /debug-config slash command (#10642) image --- .../src/config_loader/config_requirements.rs | 6 + codex-rs/tui/src/chatwidget.rs | 7 + codex-rs/tui/src/debug_config.rs | 338 ++++++++++++++++++ codex-rs/tui/src/lib.rs | 1 + codex-rs/tui/src/slash_command.rs | 3 + 5 files changed, 355 insertions(+) create mode 100644 codex-rs/tui/src/debug_config.rs diff --git a/codex-rs/core/src/config_loader/config_requirements.rs b/codex-rs/core/src/config_loader/config_requirements.rs index 660628c04f..b3e6043288 100644 --- a/codex-rs/core/src/config_loader/config_requirements.rs +++ b/codex-rs/core/src/config_loader/config_requirements.rs @@ -99,6 +99,12 @@ impl Default for ConfigRequirements { } } +impl ConfigRequirements { + pub fn exec_policy_source(&self) -> Option<&RequirementSource> { + self.exec_policy.as_ref().map(|policy| &policy.source) + } +} + #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(untagged)] pub enum McpServerIdentity { diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 58ffcc37f0..191873ca5f 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -3006,6 +3006,9 @@ impl ChatWidget { SlashCommand::Status => { self.add_status_output(); } + SlashCommand::DebugConfig => { + self.add_debug_config_output(); + } SlashCommand::Ps => { self.add_ps_output(); } @@ -3781,6 +3784,10 @@ impl ChatWidget { )); } + pub(crate) fn add_debug_config_output(&mut self) { + self.add_to_history(crate::debug_config::new_debug_config_output(&self.config)); + } + pub(crate) fn add_ps_output(&mut self) { let processes = self .unified_exec_processes diff --git a/codex-rs/tui/src/debug_config.rs b/codex-rs/tui/src/debug_config.rs new file mode 100644 index 0000000000..4202264fb2 --- /dev/null +++ b/codex-rs/tui/src/debug_config.rs @@ -0,0 +1,338 @@ +use crate::history_cell::PlainHistoryCell; +use codex_app_server_protocol::ConfigLayerSource; +use codex_core::config::Config; +use codex_core::config_loader::ConfigLayerStack; +use codex_core::config_loader::ConfigLayerStackOrdering; +use codex_core::config_loader::RequirementSource; +use codex_core::config_loader::ResidencyRequirement; +use codex_core::config_loader::SandboxModeRequirement; +use ratatui::style::Stylize; +use ratatui::text::Line; + +pub(crate) fn new_debug_config_output(config: &Config) -> PlainHistoryCell { + PlainHistoryCell::new(render_debug_config_lines(&config.config_layer_stack)) +} + +fn render_debug_config_lines(stack: &ConfigLayerStack) -> Vec> { + let mut lines = vec!["/debug-config".magenta().into(), "".into()]; + + lines.push( + "Config layer stack (lowest precedence first):" + .bold() + .into(), + ); + let layers = stack.get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true); + if layers.is_empty() { + lines.push(" ".dim().into()); + } else { + for (index, layer) in layers.iter().enumerate() { + let source = format_config_layer_source(&layer.name); + let status = if layer.is_disabled() { + "disabled" + } else { + "enabled" + }; + lines.push(format!(" {}. {source} ({status})", index + 1).into()); + if let Some(reason) = &layer.disabled_reason { + lines.push(format!(" reason: {reason}").dim().into()); + } + } + } + + let requirements = stack.requirements(); + let requirements_toml = stack.requirements_toml(); + + lines.push("".into()); + lines.push("Requirements:".bold().into()); + let mut requirement_lines = Vec::new(); + + if let Some(policies) = requirements_toml.allowed_approval_policies.as_ref() { + let value = join_or_empty(policies.iter().map(ToString::to_string).collect::>()); + requirement_lines.push(requirement_line( + "allowed_approval_policies", + value, + requirements.approval_policy.source.as_ref(), + )); + } + + if let Some(modes) = requirements_toml.allowed_sandbox_modes.as_ref() { + let value = join_or_empty( + modes + .iter() + .copied() + .map(format_sandbox_mode_requirement) + .collect::>(), + ); + requirement_lines.push(requirement_line( + "allowed_sandbox_modes", + value, + requirements.sandbox_policy.source.as_ref(), + )); + } + + if let Some(servers) = requirements_toml.mcp_servers.as_ref() { + let value = join_or_empty(servers.keys().cloned().collect::>()); + requirement_lines.push(requirement_line( + "mcp_servers", + value, + requirements + .mcp_servers + .as_ref() + .map(|sourced| &sourced.source), + )); + } + + // TODO(gt): Expand this debug output with detailed skills and rules display. + if requirements_toml.rules.is_some() { + requirement_lines.push(requirement_line( + "rules", + "configured".to_string(), + requirements.exec_policy_source(), + )); + } + + if let Some(residency) = requirements_toml.enforce_residency { + requirement_lines.push(requirement_line( + "enforce_residency", + format_residency_requirement(residency), + requirements.enforce_residency.source.as_ref(), + )); + } + + if requirement_lines.is_empty() { + lines.push(" ".dim().into()); + } else { + lines.extend(requirement_lines); + } + + lines +} + +fn requirement_line( + name: &str, + value: String, + source: Option<&RequirementSource>, +) -> Line<'static> { + let source = source + .map(ToString::to_string) + .unwrap_or_else(|| "".to_string()); + format!(" - {name}: {value} (source: {source})").into() +} + +fn join_or_empty(values: Vec) -> String { + if values.is_empty() { + "".to_string() + } else { + values.join(", ") + } +} + +fn format_config_layer_source(source: &ConfigLayerSource) -> String { + match source { + ConfigLayerSource::Mdm { domain, key } => { + format!("mdm ({domain}:{key})") + } + ConfigLayerSource::System { file } => { + format!("system ({})", file.as_path().display()) + } + ConfigLayerSource::User { file } => { + format!("user ({})", file.as_path().display()) + } + ConfigLayerSource::Project { dot_codex_folder } => { + format!( + "project ({}/config.toml)", + dot_codex_folder.as_path().display() + ) + } + ConfigLayerSource::SessionFlags => "session-flags".to_string(), + ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => { + format!("legacy managed_config.toml ({})", file.as_path().display()) + } + ConfigLayerSource::LegacyManagedConfigTomlFromMdm => { + "legacy managed_config.toml (mdm)".to_string() + } + } +} + +fn format_sandbox_mode_requirement(mode: SandboxModeRequirement) -> String { + match mode { + SandboxModeRequirement::ReadOnly => "read-only".to_string(), + SandboxModeRequirement::WorkspaceWrite => "workspace-write".to_string(), + SandboxModeRequirement::DangerFullAccess => "danger-full-access".to_string(), + SandboxModeRequirement::ExternalSandbox => "external-sandbox".to_string(), + } +} + +fn format_residency_requirement(requirement: ResidencyRequirement) -> String { + match requirement { + ResidencyRequirement::Us => "us".to_string(), + } +} + +#[cfg(test)] +mod tests { + use super::render_debug_config_lines; + use codex_app_server_protocol::ConfigLayerSource; + use codex_core::config::Constrained; + use codex_core::config_loader::ConfigLayerEntry; + use codex_core::config_loader::ConfigLayerStack; + use codex_core::config_loader::ConfigRequirements; + use codex_core::config_loader::ConfigRequirementsToml; + use codex_core::config_loader::ConstrainedWithSource; + use codex_core::config_loader::McpServerIdentity; + use codex_core::config_loader::McpServerRequirement; + use codex_core::config_loader::RequirementSource; + use codex_core::config_loader::ResidencyRequirement; + use codex_core::config_loader::SandboxModeRequirement; + use codex_core::config_loader::Sourced; + use codex_core::protocol::AskForApproval; + use codex_core::protocol::SandboxPolicy; + use codex_utils_absolute_path::AbsolutePathBuf; + use ratatui::text::Line; + use std::collections::BTreeMap; + use toml::Value as TomlValue; + + fn empty_toml_table() -> TomlValue { + TomlValue::Table(toml::map::Map::new()) + } + + fn absolute_path(path: &str) -> AbsolutePathBuf { + AbsolutePathBuf::from_absolute_path(path).expect("absolute path") + } + + fn render_to_text(lines: &[Line<'static>]) -> String { + lines + .iter() + .map(|line| { + line.spans + .iter() + .map(|span| span.content.as_ref()) + .collect::() + }) + .collect::>() + .join("\n") + } + + #[test] + fn debug_config_output_lists_all_layers_including_disabled() { + let system_file = if cfg!(windows) { + absolute_path("C:\\etc\\codex\\config.toml") + } else { + absolute_path("/etc/codex/config.toml") + }; + let project_folder = if cfg!(windows) { + absolute_path("C:\\repo\\.codex") + } else { + absolute_path("/repo/.codex") + }; + + let layers = vec![ + ConfigLayerEntry::new( + ConfigLayerSource::System { file: system_file }, + empty_toml_table(), + ), + ConfigLayerEntry::new_disabled( + ConfigLayerSource::Project { + dot_codex_folder: project_folder, + }, + empty_toml_table(), + "project is untrusted", + ), + ]; + let stack = ConfigLayerStack::new( + layers, + ConfigRequirements::default(), + ConfigRequirementsToml::default(), + ) + .expect("config layer stack"); + + let rendered = render_to_text(&render_debug_config_lines(&stack)); + assert!(rendered.contains("(enabled)")); + assert!(rendered.contains("(disabled)")); + assert!(rendered.contains("reason: project is untrusted")); + assert!(rendered.contains("Requirements:")); + assert!(rendered.contains(" ")); + } + + #[test] + fn debug_config_output_lists_requirement_sources() { + let requirements_file = if cfg!(windows) { + absolute_path("C:\\etc\\codex\\requirements.toml") + } else { + absolute_path("/etc/codex/requirements.toml") + }; + let mut requirements = ConfigRequirements::default(); + requirements.approval_policy = ConstrainedWithSource::new( + Constrained::allow_any(AskForApproval::OnRequest), + Some(RequirementSource::CloudRequirements), + ); + requirements.sandbox_policy = ConstrainedWithSource::new( + Constrained::allow_any(SandboxPolicy::ReadOnly), + Some(RequirementSource::SystemRequirementsToml { + file: requirements_file.clone(), + }), + ); + requirements.mcp_servers = Some(Sourced::new( + BTreeMap::from([( + "docs".to_string(), + McpServerRequirement { + identity: McpServerIdentity::Command { + command: "codex-mcp".to_string(), + }, + }, + )]), + RequirementSource::LegacyManagedConfigTomlFromMdm, + )); + requirements.enforce_residency = ConstrainedWithSource::new( + Constrained::allow_any(Some(ResidencyRequirement::Us)), + Some(RequirementSource::CloudRequirements), + ); + + let requirements_toml = ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::OnRequest]), + allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]), + mcp_servers: Some(BTreeMap::from([( + "docs".to_string(), + McpServerRequirement { + identity: McpServerIdentity::Command { + command: "codex-mcp".to_string(), + }, + }, + )])), + rules: None, + enforce_residency: Some(ResidencyRequirement::Us), + }; + + let user_file = if cfg!(windows) { + absolute_path("C:\\users\\alice\\.codex\\config.toml") + } else { + absolute_path("/home/alice/.codex/config.toml") + }; + let stack = ConfigLayerStack::new( + vec![ConfigLayerEntry::new( + ConfigLayerSource::User { file: user_file }, + empty_toml_table(), + )], + requirements, + requirements_toml, + ) + .expect("config layer stack"); + + let rendered = render_to_text(&render_debug_config_lines(&stack)); + assert!( + rendered.contains("allowed_approval_policies: on-request (source: cloud requirements)") + ); + assert!( + rendered.contains( + format!( + "allowed_sandbox_modes: read-only (source: {})", + requirements_file.as_path().display() + ) + .as_str(), + ) + ); + assert!(rendered.contains("mcp_servers: docs (source: MDM managed_config.toml (legacy))")); + assert!(rendered.contains("enforce_residency: us (source: cloud requirements)")); + assert!(!rendered.contains(" - rules:")); + } +} diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 2e5aee5c38..2dbc1fcaf9 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -67,6 +67,7 @@ mod collaboration_modes; mod color; pub mod custom_terminal; mod cwd_prompt; +mod debug_config; mod diff_render; mod exec_cell; mod exec_command; diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index a71b1ddbe5..e4ac5dcacf 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -33,6 +33,7 @@ pub enum SlashCommand { Diff, Mention, Status, + DebugConfig, Mcp, Apps, Logout, @@ -63,6 +64,7 @@ impl SlashCommand { SlashCommand::Mention => "mention a file", SlashCommand::Skills => "use skills to improve how Codex performs specific tasks", SlashCommand::Status => "show current session configuration and token usage", + SlashCommand::DebugConfig => "show config layers and requirement sources for debugging", SlashCommand::Ps => "list background terminals", SlashCommand::Model => "choose what model and reasoning effort to use", SlashCommand::Personality => "choose a communication style for Codex", @@ -118,6 +120,7 @@ impl SlashCommand { | SlashCommand::Mention | SlashCommand::Skills | SlashCommand::Status + | SlashCommand::DebugConfig | SlashCommand::Ps | SlashCommand::Mcp | SlashCommand::Apps From 7bcc552325b95b1f17042c0e4f928ef971f26339 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Wed, 4 Feb 2026 15:25:03 -0800 Subject: [PATCH 34/90] Added support for live updates to skills (#10478) Add a centralized FileWatcher in codex-core (using notify) that watches skill roots from the config layer stack (recursive) Send `SkillsChanged` events when relevant file system changes are detected On `SkillsChanged`: * Invalidate the skills cache immediately in ThreadManager * Emit EventMsg::SkillsUpdateAvailable to active sessions ~~* Broadcast a new app-server notification: SkillsListUpdatedNotification~~ This change does not inject new items into the event stream. That means the agent will not know about new skills, so it won't be able to implicitly invoke new skills. It also won't know about changes to existing skills, so if it has already read the contents of a modified skill, it will not honor the new behavior. This change also does not detect modifications to AGENTS.md. I plan to address these limitations in a follow-on PR modeled after #9985. Injection of new skills and AGENTS was deemed to risky, hence the need to split the feature into two stages. The changes in this PR were designed to easily accommodate the second stage once we have some other foundational changes in place. Testing: In addition to automated tests, I did manual testing to confirm that newly-created skills, deleted skills, and renamed skills are reflected in the TUI skill picker menu. Also confirmed that modifications to behaviors for explicitly-invoked skills are honored. --------- Co-authored-by: Xin Lin --- codex-rs/Cargo.lock | 1 + codex-rs/core/Cargo.toml | 1 + codex-rs/core/src/codex.rs | 38 ++- codex-rs/core/src/codex_delegate.rs | 1 + codex-rs/core/src/file_watcher.rs | 407 +++++++++++++++++++++++ codex-rs/core/src/lib.rs | 2 + codex-rs/core/src/skills/manager.rs | 38 +-- codex-rs/core/src/state/service.rs | 2 + codex-rs/core/src/thread_manager.rs | 70 +++- codex-rs/core/tests/suite/live_reload.rs | 151 +++++++++ codex-rs/core/tests/suite/mod.rs | 1 + 11 files changed, 684 insertions(+), 28 deletions(-) create mode 100644 codex-rs/core/src/file_watcher.rs create mode 100644 codex-rs/core/tests/suite/live_reload.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index d83626c4b7..ebc7ddd33c 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1564,6 +1564,7 @@ dependencies = [ "libc", "maplit", "multimap", + "notify", "once_cell", "openssl-sys", "opentelemetry_sdk", diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index f3ba5fd2cb..d686343ae7 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -58,6 +58,7 @@ indoc = { workspace = true } keyring = { workspace = true, features = ["crypto-rust"] } libc = { workspace = true } multimap = { workspace = true } +notify = { workspace = true } once_cell = { workspace = true } os_info = { workspace = true } rand = { workspace = true } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index b1d2fdf45e..4068345c39 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -118,6 +118,8 @@ use crate::error::Result as CodexResult; use crate::exec::StreamOutput; use crate::exec_policy::ExecPolicyUpdateError; use crate::feedback_tags; +use crate::file_watcher::FileWatcher; +use crate::file_watcher::FileWatcherEvent; use crate::git_info::get_git_repo_root; use crate::instructions::UserInstructions; use crate::mcp::CODEX_APPS_MCP_SERVER_NAME; @@ -252,6 +254,7 @@ impl Codex { auth_manager: Arc, models_manager: Arc, skills_manager: Arc, + file_watcher: Arc, conversation_history: InitialHistory, session_source: SessionSource, agent_control: AgentControl, @@ -388,6 +391,7 @@ impl Codex { conversation_history, session_source_clone, skills_manager, + file_watcher, agent_control, ) .instrument(session_init_span) @@ -700,6 +704,29 @@ impl Session { state.session_configuration.codex_home().clone() } + fn start_file_watcher_listener(self: &Arc) { + let mut rx = self.services.file_watcher.subscribe(); + let weak_sess = Arc::downgrade(self); + tokio::spawn(async move { + loop { + match rx.recv().await { + Ok(FileWatcherEvent::SkillsChanged { .. }) => { + let Some(sess) = weak_sess.upgrade() else { + break; + }; + let event = Event { + id: sess.next_internal_sub_id(), + msg: EventMsg::SkillsUpdateAvailable, + }; + sess.send_event_raw(event).await; + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue, + } + } + }); + } + #[allow(clippy::too_many_arguments)] fn make_turn_context( auth_manager: Option>, @@ -790,6 +817,7 @@ impl Session { initial_history: InitialHistory, session_source: SessionSource, skills_manager: Arc, + file_watcher: Arc, agent_control: AgentControl, ) -> anyhow::Result> { debug!( @@ -989,6 +1017,7 @@ impl Session { models_manager: Arc::clone(&models_manager), tool_approvals: Mutex::new(ApprovalStore::default()), skills_manager, + file_watcher, agent_control, state_db: state_db_ctx.clone(), transport_manager: TransportManager::new(), @@ -1032,6 +1061,9 @@ impl Session { sess.send_event_raw(event).await; } + // Start the watcher after SessionConfigured so it cannot emit earlier events. + sess.start_file_watcher_listener(); + // Construct sandbox_state before initialize() so it can be sent to each // MCP server immediately after it becomes ready (avoiding blocking). let sandbox_state = SandboxState { @@ -5536,6 +5568,7 @@ mod tests { mark_state_initial_context_seeded(&mut state); let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone())); + let file_watcher = Arc::new(FileWatcher::noop()); let services = SessionServices { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), @@ -5554,6 +5587,7 @@ mod tests { models_manager: Arc::clone(&models_manager), tool_approvals: Mutex::new(ApprovalStore::default()), skills_manager, + file_watcher, agent_control, state_db: None, transport_manager: TransportManager::new(), @@ -5656,6 +5690,7 @@ mod tests { mark_state_initial_context_seeded(&mut state); let skills_manager = Arc::new(SkillsManager::new(config.codex_home.clone())); + let file_watcher = Arc::new(FileWatcher::noop()); let services = SessionServices { mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())), mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()), @@ -5674,6 +5709,7 @@ mod tests { models_manager: Arc::clone(&models_manager), tool_approvals: Mutex::new(ApprovalStore::default()), skills_manager, + file_watcher, agent_control, state_db: None, transport_manager: TransportManager::new(), @@ -5844,7 +5880,7 @@ mod tests { } #[tokio::test] - async fn abort_gracefuly_emits_turn_aborted_only() { + async fn abort_gracefully_emits_turn_aborted_only() { let (sess, tc, rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { text: "hello".to_string(), diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 6499370fc3..cb5a31a0cc 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -54,6 +54,7 @@ pub(crate) async fn run_codex_thread_interactive( auth_manager, models_manager, Arc::clone(&parent_session.services.skills_manager), + Arc::clone(&parent_session.services.file_watcher), initial_history.unwrap_or(InitialHistory::New), SessionSource::SubAgent(SubAgentSource::Review), parent_session.services.agent_control.clone(), diff --git a/codex-rs/core/src/file_watcher.rs b/codex-rs/core/src/file_watcher.rs new file mode 100644 index 0000000000..afda5b6086 --- /dev/null +++ b/codex-rs/core/src/file_watcher.rs @@ -0,0 +1,407 @@ +//! Watches skill roots for changes and broadcasts coarse-grained +//! `FileWatcherEvent`s that higher-level components react to on the next turn. + +use std::collections::HashMap; +use std::collections::HashSet; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::RwLock; +use std::time::Duration; + +use notify::Event; +use notify::RecommendedWatcher; +use notify::RecursiveMode; +use notify::Watcher; +use tokio::runtime::Handle; +use tokio::sync::broadcast; +use tokio::sync::mpsc; +use tokio::time::Instant; +use tokio::time::sleep_until; +use tracing::warn; + +use crate::config::Config; +use crate::skills::loader::skill_roots_from_layer_stack_with_agents; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FileWatcherEvent { + SkillsChanged { paths: Vec }, +} + +struct WatchState { + skills_roots: HashSet, +} + +struct FileWatcherInner { + watcher: RecommendedWatcher, + watched_paths: HashMap, +} + +const WATCHER_THROTTLE_INTERVAL: Duration = Duration::from_secs(1); + +/// Coalesces bursts of paths and emits at most once per interval. +struct ThrottledPaths { + pending: HashSet, + next_allowed_at: Instant, +} + +impl ThrottledPaths { + fn new(now: Instant) -> Self { + Self { + pending: HashSet::new(), + next_allowed_at: now, + } + } + + fn add(&mut self, paths: Vec) { + self.pending.extend(paths); + } + + fn next_deadline(&self, now: Instant) -> Option { + (!self.pending.is_empty() && now < self.next_allowed_at).then_some(self.next_allowed_at) + } + + fn take_ready(&mut self, now: Instant) -> Option> { + if self.pending.is_empty() || now < self.next_allowed_at { + return None; + } + Some(self.take_with_next_allowed(now)) + } + + fn take_pending(&mut self, now: Instant) -> Option> { + if self.pending.is_empty() { + return None; + } + Some(self.take_with_next_allowed(now)) + } + + fn take_with_next_allowed(&mut self, now: Instant) -> Vec { + let mut paths: Vec = self.pending.drain().collect(); + paths.sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str())); + self.next_allowed_at = now + WATCHER_THROTTLE_INTERVAL; + paths + } +} + +pub(crate) struct FileWatcher { + inner: Option>, + state: Arc>, + tx: broadcast::Sender, +} + +impl FileWatcher { + pub(crate) fn new(_codex_home: PathBuf) -> notify::Result { + let (raw_tx, raw_rx) = mpsc::unbounded_channel(); + let raw_tx_clone = raw_tx; + let watcher = notify::recommended_watcher(move |res| { + let _ = raw_tx_clone.send(res); + })?; + let inner = FileWatcherInner { + watcher, + watched_paths: HashMap::new(), + }; + let (tx, _) = broadcast::channel(128); + let state = Arc::new(RwLock::new(WatchState { + skills_roots: HashSet::new(), + })); + let file_watcher = Self { + inner: Some(Mutex::new(inner)), + state: Arc::clone(&state), + tx: tx.clone(), + }; + file_watcher.spawn_event_loop(raw_rx, state, tx); + Ok(file_watcher) + } + + pub(crate) fn noop() -> Self { + let (tx, _) = broadcast::channel(1); + Self { + inner: None, + state: Arc::new(RwLock::new(WatchState { + skills_roots: HashSet::new(), + })), + tx, + } + } + + pub(crate) fn subscribe(&self) -> broadcast::Receiver { + self.tx.subscribe() + } + + pub(crate) fn register_config(&self, config: &Config) { + let roots = + skill_roots_from_layer_stack_with_agents(&config.config_layer_stack, &config.cwd); + for root in roots { + self.register_skills_root(root.path); + } + } + + // Bridge `notify`'s callback-based events into the Tokio runtime and + // broadcast coarse-grained change signals to subscribers. + fn spawn_event_loop( + &self, + mut raw_rx: mpsc::UnboundedReceiver>, + state: Arc>, + tx: broadcast::Sender, + ) { + if let Ok(handle) = Handle::try_current() { + handle.spawn(async move { + let now = Instant::now(); + let mut skills = ThrottledPaths::new(now); + + loop { + let now = Instant::now(); + let next_deadline = skills.next_deadline(now); + let timer_deadline = next_deadline + .unwrap_or_else(|| now + Duration::from_secs(60 * 60 * 24 * 365)); + let timer = sleep_until(timer_deadline); + tokio::pin!(timer); + + tokio::select! { + res = raw_rx.recv() => { + match res { + Some(Ok(event)) => { + let skills_paths = classify_event(&event, &state); + let now = Instant::now(); + skills.add(skills_paths); + + if let Some(paths) = skills.take_ready(now) { + let _ = tx.send(FileWatcherEvent::SkillsChanged { paths }); + } + } + Some(Err(err)) => { + warn!("file watcher error: {err}"); + } + None => { + // Flush any pending changes before shutdown so subscribers + // see the latest state. + let now = Instant::now(); + if let Some(paths) = skills.take_pending(now) { + let _ = tx.send(FileWatcherEvent::SkillsChanged { paths }); + } + break; + } + } + } + _ = &mut timer => { + let now = Instant::now(); + if let Some(paths) = skills.take_ready(now) { + let _ = tx.send(FileWatcherEvent::SkillsChanged { paths }); + } + } + } + } + }); + } else { + warn!("file watcher loop skipped: no Tokio runtime available"); + } + } + + fn register_skills_root(&self, root: PathBuf) { + { + let mut state = match self.state.write() { + Ok(state) => state, + Err(err) => err.into_inner(), + }; + state.skills_roots.insert(root.clone()); + } + self.watch_path(root, RecursiveMode::Recursive); + } + + fn watch_path(&self, path: PathBuf, mode: RecursiveMode) { + let Some(inner) = &self.inner else { + return; + }; + if !path.exists() { + return; + } + let watch_path = path; + let mut guard = match inner.lock() { + Ok(guard) => guard, + Err(err) => err.into_inner(), + }; + if let Some(existing) = guard.watched_paths.get(&watch_path) { + if *existing == RecursiveMode::Recursive || *existing == mode { + return; + } + if let Err(err) = guard.watcher.unwatch(&watch_path) { + warn!("failed to unwatch {}: {err}", watch_path.display()); + } + } + if let Err(err) = guard.watcher.watch(&watch_path, mode) { + warn!("failed to watch {}: {err}", watch_path.display()); + return; + } + guard.watched_paths.insert(watch_path, mode); + } +} + +fn classify_event(event: &Event, state: &RwLock) -> Vec { + let mut skills_paths = Vec::new(); + let skills_roots = match state.read() { + Ok(state) => state.skills_roots.clone(), + Err(err) => { + let state = err.into_inner(); + state.skills_roots.clone() + } + }; + + for path in &event.paths { + if is_skills_path(path, &skills_roots) { + skills_paths.push(path.clone()); + } + } + + skills_paths +} + +fn is_skills_path(path: &Path, roots: &HashSet) -> bool { + roots.iter().any(|root| path.starts_with(root)) +} + +#[cfg(test)] +mod tests { + use super::*; + use notify::EventKind; + use pretty_assertions::assert_eq; + use tokio::time::timeout; + + fn path(name: &str) -> PathBuf { + PathBuf::from(name) + } + + fn notify_event(paths: Vec) -> Event { + let mut event = Event::new(EventKind::Any); + for path in paths { + event = event.add_path(path); + } + event + } + + #[test] + fn throttles_and_coalesces_within_interval() { + let start = Instant::now(); + let mut throttled = ThrottledPaths::new(start); + + throttled.add(vec![path("a")]); + let first = throttled.take_ready(start).expect("first emit"); + assert_eq!(first, vec![path("a")]); + + throttled.add(vec![path("b"), path("c")]); + assert_eq!(throttled.take_ready(start), None); + + let second = throttled + .take_ready(start + WATCHER_THROTTLE_INTERVAL) + .expect("coalesced emit"); + assert_eq!(second, vec![path("b"), path("c")]); + } + + #[test] + fn flushes_pending_on_shutdown() { + let start = Instant::now(); + let mut throttled = ThrottledPaths::new(start); + + throttled.add(vec![path("a")]); + let _ = throttled.take_ready(start).expect("first emit"); + + throttled.add(vec![path("b")]); + assert_eq!(throttled.take_ready(start), None); + + let flushed = throttled + .take_pending(start) + .expect("shutdown flush emits pending paths"); + assert_eq!(flushed, vec![path("b")]); + } + + #[test] + fn classify_event_filters_to_skills_roots() { + let root = path("/tmp/skills"); + let state = RwLock::new(WatchState { + skills_roots: HashSet::from([root.clone()]), + }); + let event = notify_event(vec![ + root.join("demo/SKILL.md"), + path("/tmp/other/not-a-skill.txt"), + ]); + + let classified = classify_event(&event, &state); + assert_eq!(classified, vec![root.join("demo/SKILL.md")]); + } + + #[test] + fn classify_event_supports_multiple_roots_without_prefix_false_positives() { + let root_a = path("/tmp/skills"); + let root_b = path("/tmp/workspace/.codex/skills"); + let state = RwLock::new(WatchState { + skills_roots: HashSet::from([root_a.clone(), root_b.clone()]), + }); + let event = notify_event(vec![ + root_a.join("alpha/SKILL.md"), + path("/tmp/skills-extra/not-under-skills.txt"), + root_b.join("beta/SKILL.md"), + ]); + + let classified = classify_event(&event, &state); + assert_eq!( + classified, + vec![root_a.join("alpha/SKILL.md"), root_b.join("beta/SKILL.md")] + ); + } + + #[test] + fn register_skills_root_dedupes_state_entries() { + let watcher = FileWatcher::noop(); + let root = path("/tmp/skills"); + watcher.register_skills_root(root.clone()); + watcher.register_skills_root(root); + watcher.register_skills_root(path("/tmp/other-skills")); + + let state = watcher.state.read().expect("state lock"); + assert_eq!(state.skills_roots.len(), 2); + } + + #[tokio::test] + async fn spawn_event_loop_flushes_pending_changes_on_shutdown() { + let watcher = FileWatcher::noop(); + let root = path("/tmp/skills"); + { + let mut state = watcher.state.write().expect("state lock"); + state.skills_roots.insert(root.clone()); + } + + let (raw_tx, raw_rx) = mpsc::unbounded_channel(); + let (tx, mut rx) = broadcast::channel(8); + watcher.spawn_event_loop(raw_rx, Arc::clone(&watcher.state), tx); + + raw_tx + .send(Ok(notify_event(vec![root.join("a/SKILL.md")]))) + .expect("send first event"); + let first = timeout(Duration::from_secs(2), rx.recv()) + .await + .expect("first watcher event") + .expect("broadcast recv first"); + assert_eq!( + first, + FileWatcherEvent::SkillsChanged { + paths: vec![root.join("a/SKILL.md")] + } + ); + + raw_tx + .send(Ok(notify_event(vec![root.join("b/SKILL.md")]))) + .expect("send second event"); + drop(raw_tx); + + let second = timeout(Duration::from_secs(2), rx.recv()) + .await + .expect("second watcher event") + .expect("broadcast recv second"); + assert_eq!( + second, + FileWatcherEvent::SkillsChanged { + paths: vec![root.join("b/SKILL.md")] + } + ); + } +} diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 36fdbdc9a6..869df93d0a 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -32,6 +32,7 @@ pub mod exec; pub mod exec_env; mod exec_policy; pub mod features; +mod file_watcher; mod flags; pub mod git_info; pub mod instructions; @@ -137,6 +138,7 @@ pub use command_safety::is_safe_command; pub use exec_policy::ExecPolicyError; pub use exec_policy::check_execpolicy_for_warnings; pub use exec_policy::load_exec_policy; +pub use file_watcher::FileWatcherEvent; pub use safety::get_platform_sandbox; pub use tools::spec::parse_tool_input_schema; pub use turn_metadata::build_turn_metadata_header; diff --git a/codex-rs/core/src/skills/manager.rs b/codex-rs/core/src/skills/manager.rs index 85e0bf20eb..61dcd98f52 100644 --- a/codex-rs/core/src/skills/manager.rs +++ b/codex-rs/core/src/skills/manager.rs @@ -6,6 +6,7 @@ use std::sync::RwLock; use codex_utils_absolute_path::AbsolutePathBuf; use toml::Value as TomlValue; +use tracing::info; use tracing::warn; use crate::config::Config; @@ -51,14 +52,11 @@ impl SkillsManager { skill_roots_from_layer_stack_with_agents(&config.config_layer_stack, &config.cwd); let mut outcome = load_skills_from_roots(roots); outcome.disabled_paths = disabled_paths_from_stack(&config.config_layer_stack); - match self.cache_by_cwd.write() { - Ok(mut cache) => { - cache.insert(cwd.to_path_buf(), outcome.clone()); - } - Err(err) => { - err.into_inner().insert(cwd.to_path_buf(), outcome.clone()); - } - } + let mut cache = match self.cache_by_cwd.write() { + Ok(cache) => cache, + Err(err) => err.into_inner(), + }; + cache.insert(cwd.to_path_buf(), outcome.clone()); outcome } @@ -109,22 +107,22 @@ impl SkillsManager { let roots = skill_roots_from_layer_stack_with_agents(&config_layer_stack, cwd); let mut outcome = load_skills_from_roots(roots); outcome.disabled_paths = disabled_paths_from_stack(&config_layer_stack); - match self.cache_by_cwd.write() { - Ok(mut cache) => { - cache.insert(cwd.to_path_buf(), outcome.clone()); - } - Err(err) => { - err.into_inner().insert(cwd.to_path_buf(), outcome.clone()); - } - } + let mut cache = match self.cache_by_cwd.write() { + Ok(cache) => cache, + Err(err) => err.into_inner(), + }; + cache.insert(cwd.to_path_buf(), outcome.clone()); outcome } pub fn clear_cache(&self) { - match self.cache_by_cwd.write() { - Ok(mut cache) => cache.clear(), - Err(err) => err.into_inner().clear(), - } + let mut cache = match self.cache_by_cwd.write() { + Ok(cache) => cache, + Err(err) => err.into_inner(), + }; + let cleared = cache.len(); + cache.clear(); + info!("skills cache cleared ({cleared} entries)"); } } diff --git a/codex-rs/core/src/state/service.rs b/codex-rs/core/src/state/service.rs index d7788f71cb..e9a028bfd5 100644 --- a/codex-rs/core/src/state/service.rs +++ b/codex-rs/core/src/state/service.rs @@ -5,6 +5,7 @@ use crate::RolloutRecorder; use crate::agent::AgentControl; use crate::analytics_client::AnalyticsEventsClient; use crate::exec_policy::ExecPolicyManager; +use crate::file_watcher::FileWatcher; use crate::mcp_connection_manager::McpConnectionManager; use crate::models_manager::manager::ModelsManager; use crate::skills::SkillsManager; @@ -33,6 +34,7 @@ pub(crate) struct SessionServices { pub(crate) otel_manager: OtelManager, pub(crate) tool_approvals: Mutex, pub(crate) skills_manager: Arc, + pub(crate) file_watcher: Arc, pub(crate) agent_control: AgentControl, pub(crate) state_db: Option, pub(crate) transport_manager: TransportManager, diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 37bd0efabc..e702d9a004 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -11,6 +11,8 @@ use crate::codex_thread::CodexThread; use crate::config::Config; use crate::error::CodexErr; use crate::error::Result as CodexResult; +use crate::file_watcher::FileWatcher; +use crate::file_watcher::FileWatcherEvent; use crate::models_manager::manager::ModelsManager; use crate::protocol::Event; use crate::protocol::EventMsg; @@ -31,12 +33,56 @@ use std::path::PathBuf; use std::sync::Arc; #[cfg(any(test, feature = "test-support"))] use tempfile::TempDir; +use tokio::runtime::Handle; +#[cfg(any(test, feature = "test-support"))] +use tokio::runtime::RuntimeFlavor; use tokio::sync::RwLock; use tokio::sync::broadcast; use tracing::warn; const THREAD_CREATED_CHANNEL_CAPACITY: usize = 1024; +fn build_file_watcher(codex_home: PathBuf, skills_manager: Arc) -> Arc { + #[cfg(any(test, feature = "test-support"))] + if let Ok(handle) = Handle::try_current() + && handle.runtime_flavor() == RuntimeFlavor::CurrentThread + { + // The real watcher spins background tasks that can starve the + // current-thread test runtime and cause event waits to time out. + // Integration tests compile with the `test-support` feature. + warn!("using noop file watcher under current-thread test runtime"); + return Arc::new(FileWatcher::noop()); + } + + let file_watcher = match FileWatcher::new(codex_home) { + Ok(file_watcher) => Arc::new(file_watcher), + Err(err) => { + warn!("failed to initialize file watcher: {err}"); + Arc::new(FileWatcher::noop()) + } + }; + + let mut rx = file_watcher.subscribe(); + let skills_manager = Arc::clone(&skills_manager); + if let Ok(handle) = Handle::try_current() { + handle.spawn(async move { + loop { + match rx.recv().await { + Ok(FileWatcherEvent::SkillsChanged { .. }) => { + skills_manager.clear_cache(); + } + Err(broadcast::error::RecvError::Closed) => break, + Err(broadcast::error::RecvError::Lagged(_)) => continue, + } + } + }); + } else { + warn!("file watcher listener skipped: no Tokio runtime available"); + } + + file_watcher +} + /// Represents a newly created Codex thread (formerly called a conversation), including the first event /// (which is [`EventMsg::SessionConfigured`]). pub struct NewThread { @@ -62,6 +108,7 @@ pub(crate) struct ThreadManagerState { auth_manager: Arc, models_manager: Arc, skills_manager: Arc, + file_watcher: Arc, session_source: SessionSource, #[cfg(any(test, feature = "test-support"))] #[allow(dead_code)] @@ -76,15 +123,15 @@ impl ThreadManager { session_source: SessionSource, ) -> Self { let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY); + let skills_manager = Arc::new(SkillsManager::new(codex_home.clone())); + let file_watcher = build_file_watcher(codex_home.clone(), Arc::clone(&skills_manager)); Self { state: Arc::new(ThreadManagerState { threads: Arc::new(RwLock::new(HashMap::new())), thread_created_tx, - models_manager: Arc::new(ModelsManager::new( - codex_home.clone(), - auth_manager.clone(), - )), - skills_manager: Arc::new(SkillsManager::new(codex_home)), + models_manager: Arc::new(ModelsManager::new(codex_home, auth_manager.clone())), + skills_manager, + file_watcher, auth_manager, session_source, #[cfg(any(test, feature = "test-support"))] @@ -116,16 +163,19 @@ impl ThreadManager { ) -> Self { let auth_manager = AuthManager::from_auth_for_testing(auth); let (thread_created_tx, _) = broadcast::channel(THREAD_CREATED_CHANNEL_CAPACITY); + let skills_manager = Arc::new(SkillsManager::new(codex_home.clone())); + let file_watcher = build_file_watcher(codex_home.clone(), Arc::clone(&skills_manager)); Self { state: Arc::new(ThreadManagerState { threads: Arc::new(RwLock::new(HashMap::new())), thread_created_tx, models_manager: Arc::new(ModelsManager::with_provider( - codex_home.clone(), + codex_home, auth_manager.clone(), provider, )), - skills_manager: Arc::new(SkillsManager::new(codex_home)), + skills_manager, + file_watcher, auth_manager, session_source: SessionSource::Exec, #[cfg(any(test, feature = "test-support"))] @@ -143,6 +193,10 @@ impl ThreadManager { self.state.skills_manager.clone() } + pub fn subscribe_file_watcher(&self) -> broadcast::Receiver { + self.state.file_watcher.subscribe() + } + pub fn get_models_manager(&self) -> Arc { self.state.models_manager.clone() } @@ -380,6 +434,7 @@ impl ThreadManagerState { session_source: SessionSource, dynamic_tools: Vec, ) -> CodexResult { + self.file_watcher.register_config(&config); let CodexSpawnOk { codex, thread_id, .. } = Codex::spawn( @@ -387,6 +442,7 @@ impl ThreadManagerState { auth_manager, Arc::clone(&self.models_manager), Arc::clone(&self.skills_manager), + Arc::clone(&self.file_watcher), initial_history, session_source, agent_control, diff --git a/codex-rs/core/tests/suite/live_reload.rs b/codex-rs/core/tests/suite/live_reload.rs new file mode 100644 index 0000000000..e46c77128f --- /dev/null +++ b/codex-rs/core/tests/suite/live_reload.rs @@ -0,0 +1,151 @@ +#![allow(clippy::expect_used, clippy::unwrap_used)] + +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::time::Duration; + +use anyhow::Result; +use codex_core::config::ProjectConfig; +use codex_core::protocol::AskForApproval; +use codex_core::protocol::EventMsg; +use codex_core::protocol::Op; +use codex_core::protocol::SandboxPolicy; +use codex_protocol::config_types::ReasoningSummary; +use codex_protocol::config_types::TrustLevel; +use codex_protocol::user_input::UserInput; +use core_test_support::responses; +use core_test_support::responses::ResponsesRequest; +use core_test_support::responses::mount_sse_sequence; +use core_test_support::responses::start_mock_server; +use core_test_support::test_codex::TestCodex; +use core_test_support::test_codex::test_codex; +use core_test_support::wait_for_event; +use tokio::time::timeout; + +fn enable_trusted_project(config: &mut codex_core::config::Config) { + config.active_project = ProjectConfig { + trust_level: Some(TrustLevel::Trusted), + }; +} + +fn write_skill(home: &Path, name: &str, description: &str, body: &str) -> PathBuf { + let skill_dir = home.join("skills").join(name); + fs::create_dir_all(&skill_dir).expect("create skill dir"); + let contents = format!("---\nname: {name}\ndescription: {description}\n---\n\n{body}\n"); + let path = skill_dir.join("SKILL.md"); + fs::write(&path, contents).expect("write skill"); + path +} + +fn contains_skill_body(request: &ResponsesRequest, skill_body: &str) -> bool { + request + .message_input_texts("user") + .iter() + .any(|text| text.contains(skill_body) && text.contains("")) +} + +async fn submit_skill_turn(test: &TestCodex, skill_path: PathBuf, prompt: &str) -> Result<()> { + let session_model = test.session_configured.model.clone(); + test.codex + .submit(Op::UserTurn { + items: vec![ + UserInput::Text { + text: prompt.to_string(), + text_elements: Vec::new(), + }, + UserInput::Skill { + name: "demo".to_string(), + path: skill_path, + }, + ], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::DangerFullAccess, + model: session_model, + effort: None, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + + wait_for_event(test.codex.as_ref(), |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn live_skills_reload_refreshes_skill_cache_after_skill_change() -> Result<()> { + let server = start_mock_server().await; + let responses = mount_sse_sequence( + &server, + vec![ + responses::sse(vec![responses::ev_completed("resp-1")]), + responses::sse(vec![responses::ev_completed("resp-2")]), + ], + ) + .await; + + let skill_v1 = "skill body v1"; + let skill_v2 = "skill body v2"; + let mut builder = test_codex() + .with_pre_build_hook(move |home| { + write_skill(home, "demo", "demo skill", skill_v1); + }) + .with_config(|config| { + enable_trusted_project(config); + }); + let test = builder.build(&server).await?; + + let skill_path = std::fs::canonicalize(test.codex_home_path().join("skills/demo/SKILL.md"))?; + + submit_skill_turn(&test, skill_path.clone(), "please use $demo").await?; + let first_request = responses + .requests() + .first() + .cloned() + .expect("first request captured"); + assert!( + contains_skill_body(&first_request, skill_v1), + "expected initial skill body in request" + ); + + write_skill(test.codex_home_path(), "demo", "demo skill", skill_v2); + + let saw_skills_update = timeout(Duration::from_secs(5), async { + loop { + match test.codex.next_event().await { + Ok(event) => { + if matches!(event.msg, EventMsg::SkillsUpdateAvailable) { + break; + } + } + Err(err) => panic!("event stream ended unexpectedly: {err}"), + } + } + }) + .await; + + if saw_skills_update.is_err() { + // Some environments do not reliably surface file watcher events for + // skill changes. Clear the cache explicitly so we can still validate + // that the updated skill body is injected on the next turn. + test.thread_manager.skills_manager().clear_cache(); + } + + submit_skill_turn(&test, skill_path.clone(), "please use $demo again").await?; + let last_request = responses + .last_request() + .expect("request captured after skill update"); + + assert!( + contains_skill_body(&last_request, skill_v2), + "expected updated skill body after reload" + ); + + Ok(()) +} diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 47a0829898..8b057c5e16 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -81,6 +81,7 @@ mod json_result; mod list_dir; mod list_models; mod live_cli; +mod live_reload; mod model_info_overrides; mod model_overrides; mod model_tools; From f9c38f531c66248cafe2e09563fb5bfa7495c8aa Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 4 Feb 2026 15:40:33 -0800 Subject: [PATCH 35/90] add none personality option (#10688) - add none personality enum value and empty placeholder behavior\n- add docs/schema updates and e2e coverage --- .../schema/json/ClientRequest.json | 1 + .../codex_app_server_protocol.schemas.json | 1 + .../schema/json/v2/ThreadResumeParams.json | 1 + .../schema/json/v2/ThreadStartParams.json | 1 + .../schema/json/v2/TurnStartParams.json | 1 + .../schema/typescript/Personality.ts | 2 +- codex-rs/app-server/README.md | 2 + codex-rs/core/config.schema.json | 1 + codex-rs/core/tests/common/responses.rs | 4 + codex-rs/core/tests/suite/personality.rs | 153 +++++++++++++----- codex-rs/docs/codex_mcp_interface.md | 2 + codex-rs/docs/protocol_v1.md | 3 + codex-rs/protocol/src/config_types.rs | 1 + codex-rs/protocol/src/openai_models.rs | 21 +++ codex-rs/tui/src/app.rs | 1 + codex-rs/tui/src/chatwidget.rs | 2 + 16 files changed, 157 insertions(+), 40 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 9b676f8e52..4a40cc9ce9 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1171,6 +1171,7 @@ }, "Personality": { "enum": [ + "none", "friendly", "pragmatic" ], 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 f0b16e2358..87220712eb 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 @@ -12120,6 +12120,7 @@ }, "Personality": { "enum": [ + "none", "friendly", "pragmatic" ], 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 cc05de490a..8167ea779e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -251,6 +251,7 @@ }, "Personality": { "enum": [ + "none", "friendly", "pragmatic" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index 03bed7f93c..f2b0052e6d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -29,6 +29,7 @@ }, "Personality": { "enum": [ + "none", "friendly", "pragmatic" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 6a739b31ac..be351a48ea 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -66,6 +66,7 @@ }, "Personality": { "enum": [ + "none", "friendly", "pragmatic" ], diff --git a/codex-rs/app-server-protocol/schema/typescript/Personality.ts b/codex-rs/app-server-protocol/schema/typescript/Personality.ts index b9ccad4dc2..45165f4e33 100644 --- a/codex-rs/app-server-protocol/schema/typescript/Personality.ts +++ b/codex-rs/app-server-protocol/schema/typescript/Personality.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type Personality = "friendly" | "pragmatic"; +export type Personality = "none" | "friendly" | "pragmatic"; diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 055ca6f32a..69a7524717 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -147,6 +147,8 @@ Start a fresh thread when you need a new Codex conversation. { "method": "thread/started", "params": { "thread": { … } } } ``` +Valid `personality` values are `"friendly"`, `"pragmatic"`, and `"none"`. When `"none"` is selected, the personality placeholder is replaced with an empty string. + To continue a stored session, call `thread/resume` with the `thread.id` you previously recorded. The response shape matches `thread/start`, and no additional notifications are emitted. You can also pass the same configuration overrides supported by `thread/start`, such as `personality`: ```json diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 138b71e2dc..8e9550c092 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -708,6 +708,7 @@ }, "Personality": { "enum": [ + "none", "friendly", "pragmatic" ], diff --git a/codex-rs/core/tests/common/responses.rs b/codex-rs/core/tests/common/responses.rs index 86a332874e..7d3b077085 100644 --- a/codex-rs/core/tests/common/responses.rs +++ b/codex-rs/core/tests/common/responses.rs @@ -375,6 +375,10 @@ pub fn sse(events: Vec) -> String { out } +pub fn sse_completed(id: &str) -> String { + sse(vec![ev_response_created(id), ev_completed(id)]) +} + /// Convenience: SSE event for a completed response with a specific id. pub fn ev_completed(id: &str) -> Value { serde_json::json!({ diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index 919ce852ed..ff2c0cfec0 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -19,12 +19,10 @@ 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; -use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_models_once; use core_test_support::responses::mount_sse_once; use core_test_support::responses::mount_sse_sequence; -use core_test_support::responses::sse; +use core_test_support::responses::sse_completed; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; use core_test_support::test_codex::test_codex; @@ -78,11 +76,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let resp_mock = mount_sse_once( - &server, - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - ) - .await; + let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { @@ -128,11 +122,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result< skip_if_no_network!(Ok(())); let server = start_mock_server().await; - let resp_mock = mount_sse_once( - &server, - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - ) - .await; + let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; let mut builder = test_codex() .with_model("gpt-5.2-codex") .with_config(|config| { @@ -181,6 +171,111 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result< Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; + let mut builder = test_codex() + .with_model("gpt-5.2-codex") + .with_config(|config| { + config.features.disable(Feature::RemoteModels); + config.features.enable(Feature::Personality); + config.personality = Some(Personality::None); + }); + let test = builder.build(&server).await?; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: test.config.approval_policy.value(), + sandbox_policy: SandboxPolicy::ReadOnly, + model: test.session_configured.model.clone(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let request = resp_mock.single_request(); + let instructions_text = request.instructions_text(); + assert!( + !instructions_text.contains(LOCAL_FRIENDLY_TEMPLATE), + "expected no friendly personality template, got: {instructions_text:?}" + ); + assert!( + !instructions_text.contains(LOCAL_PRAGMATIC_TEMPLATE), + "expected no pragmatic personality template, got: {instructions_text:?}" + ); + assert!( + !instructions_text.contains("{{ personality }}"), + "expected personality placeholder to be removed, got: {instructions_text:?}" + ); + + let developer_texts = request.message_input_texts("developer"); + assert!( + !developer_texts + .iter() + .any(|text| text.contains("")), + "did not expect a personality update message when personality is None" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn default_personality_is_friendly_without_config_toml() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; + let mut builder = test_codex() + .with_model("gpt-5.2-codex") + .with_config(|config| { + config.features.disable(Feature::RemoteModels); + config.features.enable(Feature::Personality); + }); + let test = builder.build(&server).await?; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: test.config.approval_policy.value(), + sandbox_policy: SandboxPolicy::ReadOnly, + model: test.session_configured.model.clone(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let request = resp_mock.single_request(); + let instructions_text = request.instructions_text(); + assert!( + instructions_text.contains(LOCAL_FRIENDLY_TEMPLATE), + "expected default friendly template, got: {instructions_text:?}" + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); @@ -188,10 +283,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> let server = start_mock_server().await; let resp_mock = mount_sse_sequence( &server, - vec![ - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), - ], + vec![sse_completed("resp-1"), sse_completed("resp-2")], ) .await; let mut builder = test_codex() @@ -287,10 +379,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho let server = start_mock_server().await; let resp_mock = mount_sse_sequence( &server, - vec![ - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), - ], + vec![sse_completed("resp-1"), sse_completed("resp-2")], ) .await; let mut builder = test_codex() @@ -397,10 +486,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()> let server = start_mock_server().await; let resp_mock = mount_sse_sequence( &server, - vec![ - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), - ], + vec![sse_completed("resp-1"), sse_completed("resp-2")], ) .await; let mut builder = test_codex() @@ -537,11 +623,7 @@ async fn ignores_remote_personality_if_remote_models_disabled() -> anyhow::Resul ) .await; - let resp_mock = mount_sse_once( - &server, - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - ) - .await; + let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) @@ -657,11 +739,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow ) .await; - let resp_mock = mount_sse_once( - &server, - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - ) - .await; + let resp_mock = mount_sse_once(&server, sse_completed("resp-1")).await; let mut builder = test_codex() .with_auth(codex_core::CodexAuth::create_dummy_chatgpt_auth_for_testing()) @@ -774,10 +852,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - let resp_mock = mount_sse_sequence( &server, - vec![ - sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), - sse(vec![ev_response_created("resp-2"), ev_completed("resp-2")]), - ], + vec![sse_completed("resp-1"), sse_completed("resp-2")], ) .await; diff --git a/codex-rs/docs/codex_mcp_interface.md b/codex-rs/docs/codex_mcp_interface.md index 8847b6b4ee..8b1b5f7045 100644 --- a/codex-rs/docs/codex_mcp_interface.md +++ b/codex-rs/docs/codex_mcp_interface.md @@ -73,6 +73,8 @@ Send input to the active turn: - `sendUserMessage` → enqueue items to the conversation - `sendUserTurn` → structured turn with explicit `cwd`, `approvalPolicy`, `sandboxPolicy`, `model`, optional `effort`, `summary`, optional `personality`, and optional `outputSchema` (JSON Schema for the final assistant message) +Valid `personality` values are `friendly`, `pragmatic`, and `none`. When `none` is selected, the personality placeholder is replaced with an empty string. + For v2 threads, `turn/start` also accepts `outputSchema` to constrain the final assistant message for that turn. Interrupt a running turn: `interruptConversation`. diff --git a/codex-rs/docs/protocol_v1.md b/codex-rs/docs/protocol_v1.md index 8f68ecc9a6..4d4e5c147c 100644 --- a/codex-rs/docs/protocol_v1.md +++ b/codex-rs/docs/protocol_v1.md @@ -72,6 +72,9 @@ For complete documentation of the `Op` and `EventMsg` variants, refer to [protoc - `Op::UserInputAnswer` – Provide answers for a `request_user_input` tool call - `Op::ListSkills` – Request skills for one or more cwd values (optionally `force_reload`) - `Op::UserTurn` and `Op::OverrideTurnContext` accept an optional `personality` override that updates the model’s communication style + +Valid `personality` values are `friendly`, `pragmatic`, and `none`. When `none` is selected, the personality placeholder is replaced with an empty string. + - `EventMsg` - `EventMsg::AgentMessage` – Messages from the `Model` - `EventMsg::AgentMessageContentDelta` – Streaming assistant text diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index aa28e1a6fa..f7d7b4f431 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -96,6 +96,7 @@ pub enum WindowsSandboxLevel { #[serde(rename_all = "lowercase")] #[strum(serialize_all = "lowercase")] pub enum Personality { + None, Friendly, Pragmatic, } diff --git a/codex-rs/protocol/src/openai_models.rs b/codex-rs/protocol/src/openai_models.rs index 90cf34f393..36298e9ca5 100644 --- a/codex-rs/protocol/src/openai_models.rs +++ b/codex-rs/protocol/src/openai_models.rs @@ -335,6 +335,7 @@ impl ModelInstructionsVariables { pub fn get_personality_message(&self, personality: Option) -> Option { if let Some(personality) = personality { match personality { + Personality::None => Some(String::new()), Personality::Friendly => self.personality_friendly.clone(), Personality::Pragmatic => self.personality_pragmatic.clone(), } @@ -546,6 +547,10 @@ mod tests { model.get_model_instructions(Some(Personality::Pragmatic)), "Hello\n" ); + assert_eq!( + model.get_model_instructions(Some(Personality::None)), + "Hello\n" + ); assert_eq!(model.get_model_instructions(None), "Hello\n"); let model_no_personality = test_model(Some(ModelMessages { @@ -564,6 +569,10 @@ mod tests { model_no_personality.get_model_instructions(Some(Personality::Pragmatic)), "Hello\n" ); + assert_eq!( + model_no_personality.get_model_instructions(Some(Personality::None)), + "Hello\n" + ); assert_eq!(model_no_personality.get_model_instructions(None), "Hello\n"); } @@ -603,6 +612,10 @@ mod tests { personality_variables.get_personality_message(Some(Personality::Pragmatic)), Some("pragmatic".to_string()) ); + assert_eq!( + personality_variables.get_personality_message(Some(Personality::None)), + Some(String::new()) + ); assert_eq!( personality_variables.get_personality_message(None), Some("default".to_string()) @@ -621,6 +634,10 @@ mod tests { personality_variables.get_personality_message(Some(Personality::Pragmatic)), None ); + assert_eq!( + personality_variables.get_personality_message(Some(Personality::None)), + Some(String::new()) + ); assert_eq!( personality_variables.get_personality_message(None), Some("default".to_string()) @@ -639,6 +656,10 @@ mod tests { personality_variables.get_personality_message(Some(Personality::Pragmatic)), Some("pragmatic".to_string()) ); + assert_eq!( + personality_variables.get_personality_message(Some(Personality::None)), + Some(String::new()) + ); assert_eq!(personality_variables.get_personality_message(None), None); } } diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 878d4d5da2..99164efa61 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -2326,6 +2326,7 @@ impl App { fn personality_label(personality: Personality) -> &'static str { match personality { + Personality::None => "None", Personality::Friendly => "Friendly", Personality::Pragmatic => "Pragmatic", } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 191873ca5f..f29403f68c 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5505,6 +5505,7 @@ impl ChatWidget { fn personality_label(personality: Personality) -> &'static str { match personality { + Personality::None => "None", Personality::Friendly => "Friendly", Personality::Pragmatic => "Pragmatic", } @@ -5512,6 +5513,7 @@ impl ChatWidget { fn personality_description(personality: Personality) -> &'static str { match personality { + Personality::None => "No personality instructions.", Personality::Friendly => "Warm, collaborative, and helpful.", Personality::Pragmatic => "Concise, task-focused, and direct.", } From 5ea107a08857e44001fe156a67b7d8536d5db66b Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Wed, 4 Feb 2026 16:12:47 -0800 Subject: [PATCH 36/90] feat(app-server, core): allow text + image content items for dynamic tool outputs (#10567) Took over the work that @aaronl-openai started here: https://github.com/openai/codex/pull/10397 Now that app-server clients are able to set up custom tools (called `dynamic_tools` in app-server), we should expose a way for clients to pass in not just text, but also image outputs. This is something the Responses API already supports for function call outputs, where you can pass in either a string or an array of content outputs (text, image, file): https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-function_tool_call_output-output-array-input_image So let's just plumb it through in Codex (with the caveat that we only support text and image for now). This is implemented end-to-end across app-server v2 protocol types and core tool handling. ## Breaking API change NOTE: This introduces a breaking change with dynamic tools, but I think it's ok since this concept was only recently introduced (https://github.com/openai/codex/pull/9539) and it's better to get the API contract correct. I don't think there are any real consumers of this yet (not even the Codex App). Old shape: `{ "output": "dynamic-ok", "success": true }` New shape: ``` { "contentItems": [ { "type": "inputText", "text": "dynamic-ok" }, { "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" } ] "success": true } ``` --- .../schema/json/ClientRequest.json | 30 +- .../schema/json/DynamicToolCallResponse.json | 55 +++- .../schema/json/EventMsg.json | 30 +- .../schema/json/ServerNotification.json | 30 +- .../codex_app_server_protocol.schemas.json | 113 +++++-- .../json/v1/ForkConversationResponse.json | 30 +- .../json/v1/ResumeConversationParams.json | 30 +- .../json/v1/ResumeConversationResponse.json | 30 +- .../v1/SessionConfiguredNotification.json | 30 +- .../RawResponseItemCompletedNotification.json | 30 +- .../schema/json/v2/ThreadResumeParams.json | 30 +- .../typescript/FunctionCallOutputBody.ts | 6 + .../typescript/FunctionCallOutputPayload.ts | 10 +- .../schema/typescript/index.ts | 1 + .../v2/DynamicToolCallOutputContentItem.ts | 5 + .../typescript/v2/DynamicToolCallResponse.ts | 3 +- .../schema/typescript/v2/index.ts | 1 + .../app-server-protocol/src/protocol/v2.rs | 83 ++++- codex-rs/app-server/README.md | 36 +++ .../app-server/src/bespoke_event_handling.rs | 6 +- codex-rs/app-server/src/dynamic_tools.rs | 25 +- .../tests/suite/v2/dynamic_tools.rs | 186 ++++++++++- codex-rs/core/src/client_common.rs | 7 +- codex-rs/core/src/codex.rs | 35 ++- codex-rs/core/src/context_manager/history.rs | 27 +- .../core/src/context_manager/history_tests.rs | 119 +++---- .../core/src/context_manager/normalize.rs | 5 +- codex-rs/core/src/mcp_tool_call.rs | 4 +- codex-rs/core/src/stream_events_utils.rs | 8 +- codex-rs/core/src/tools/context.rs | 103 ++++-- .../core/src/tools/handlers/apply_patch.rs | 13 +- codex-rs/core/src/tools/handlers/collab.rs | 29 +- codex-rs/core/src/tools/handlers/dynamic.rs | 17 +- .../core/src/tools/handlers/grep_files.rs | 7 +- codex-rs/core/src/tools/handlers/list_dir.rs | 4 +- codex-rs/core/src/tools/handlers/mcp.rs | 20 +- .../core/src/tools/handlers/mcp_resource.rs | 28 +- codex-rs/core/src/tools/handlers/plan.rs | 4 +- codex-rs/core/src/tools/handlers/read_file.rs | 4 +- .../src/tools/handlers/request_user_input.rs | 4 +- codex-rs/core/src/tools/handlers/shell.rs | 4 +- codex-rs/core/src/tools/handlers/test_sync.rs | 4 +- .../core/src/tools/handlers/unified_exec.rs | 4 +- .../core/src/tools/handlers/view_image.rs | 4 +- codex-rs/core/src/tools/parallel.rs | 3 +- codex-rs/core/src/tools/router.rs | 4 +- codex-rs/core/tests/suite/client.rs | 5 +- codex-rs/protocol/src/dynamic_tools.rs | 13 +- codex-rs/protocol/src/models.rs | 292 ++++++++++++++---- 49 files changed, 1103 insertions(+), 468 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputBody.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallOutputContentItem.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 4a40cc9ce9..54041b40f2 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -480,6 +480,19 @@ }, "type": "object" }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -526,19 +539,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -548,7 +552,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, diff --git a/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json b/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json index 662d3bda4f..e0e29641d2 100644 --- a/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json +++ b/codex-rs/app-server-protocol/schema/json/DynamicToolCallResponse.json @@ -1,15 +1,64 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DynamicToolCallOutputContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "inputText" + ], + "title": "InputTextDynamicToolCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextDynamicToolCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "imageUrl": { + "type": "string" + }, + "type": { + "enum": [ + "inputImage" + ], + "title": "InputImageDynamicToolCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "imageUrl", + "type" + ], + "title": "InputImageDynamicToolCallOutputContentItem", + "type": "object" + } + ] + } + }, "properties": { - "output": { - "type": "string" + "contentItems": { + "items": { + "$ref": "#/definitions/DynamicToolCallOutputContentItem" + }, + "type": "array" }, "success": { "type": "boolean" } }, "required": [ - "output", + "contentItems", "success" ], "title": "DynamicToolCallResponse", diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index ab556a77cf..f399912e03 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -2864,6 +2864,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -2910,19 +2923,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -2932,7 +2936,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index f6d10c11c2..0e2f94f4d8 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -3484,6 +3484,19 @@ ], "type": "object" }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -3530,19 +3543,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -3552,7 +3556,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 87220712eb..9b22ff2207 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 @@ -2312,6 +2312,50 @@ ], "type": "object" }, + "DynamicToolCallOutputContentItem": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "type": { + "enum": [ + "inputText" + ], + "title": "InputTextDynamicToolCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "InputTextDynamicToolCallOutputContentItem", + "type": "object" + }, + { + "properties": { + "imageUrl": { + "type": "string" + }, + "type": { + "enum": [ + "inputImage" + ], + "title": "InputImageDynamicToolCallOutputContentItemType", + "type": "string" + } + }, + "required": [ + "imageUrl", + "type" + ], + "title": "InputImageDynamicToolCallOutputContentItem", + "type": "object" + } + ] + }, "DynamicToolCallParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -2342,15 +2386,18 @@ "DynamicToolCallResponse": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { - "output": { - "type": "string" + "contentItems": { + "items": { + "$ref": "#/definitions/DynamicToolCallOutputContentItem" + }, + "type": "array" }, "success": { "type": "boolean" } }, "required": [ - "output", + "contentItems", "success" ], "title": "DynamicToolCallResponse", @@ -5044,6 +5091,19 @@ "title": "ForkConversationResponse", "type": "object" }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -5090,19 +5150,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -5112,7 +5163,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, @@ -11246,6 +11297,19 @@ ], "type": "string" }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/v2/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -11292,19 +11356,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/v2/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/v2/FunctionCallOutputBody" }, "success": { "type": [ @@ -11314,7 +11369,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 215c80b28d..a5838e89e7 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ForkConversationResponse.json @@ -2864,6 +2864,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -2910,19 +2923,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -2932,7 +2936,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 1261306e61..9ce52963d9 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationParams.json @@ -98,6 +98,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -144,19 +157,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -166,7 +170,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 bd6d80b6ed..718b17aa28 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/ResumeConversationResponse.json @@ -2864,6 +2864,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -2910,19 +2923,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -2932,7 +2936,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 a61d4141a6..a85b78281b 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v1/SessionConfiguredNotification.json @@ -2864,6 +2864,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -2910,19 +2923,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -2932,7 +2936,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 1b307c9b89..c1e36ad8e8 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/RawResponseItemCompletedNotification.json @@ -65,6 +65,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -111,19 +124,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -133,7 +137,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, 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 8167ea779e..ed1d5274e0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -74,6 +74,19 @@ } ] }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "$ref": "#/definitions/FunctionCallOutputContentItem" + }, + "type": "array" + } + ] + }, "FunctionCallOutputContentItem": { "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", "oneOf": [ @@ -120,19 +133,10 @@ ] }, "FunctionCallOutputPayload": { - "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`content` preserves the historical plain-string payload so downstream integrations (tests, logging, etc.) can keep treating tool output as `String`. When an MCP server returns richer data we additionally populate `content_items` with the structured form that the Responses API understands.", + "description": "The payload we send back to OpenAI when reporting a tool call result.\n\n`body` serializes directly as the wire value for `function_call_output.output`. `success` remains internal metadata for downstream handling.", "properties": { - "content": { - "type": "string" - }, - "content_items": { - "items": { - "$ref": "#/definitions/FunctionCallOutputContentItem" - }, - "type": [ - "array", - "null" - ] + "body": { + "$ref": "#/definitions/FunctionCallOutputBody" }, "success": { "type": [ @@ -142,7 +146,7 @@ } }, "required": [ - "content" + "body" ], "type": "object" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputBody.ts b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputBody.ts new file mode 100644 index 0000000000..6bcb7e25d6 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputBody.ts @@ -0,0 +1,6 @@ +// 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 { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; + +export type FunctionCallOutputBody = string | Array; diff --git a/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts index 94370f582d..6376c5b8eb 100644 --- a/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts +++ b/codex-rs/app-server-protocol/schema/typescript/FunctionCallOutputPayload.ts @@ -1,14 +1,12 @@ // 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 { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; +import type { FunctionCallOutputBody } from "./FunctionCallOutputBody"; /** * The payload we send back to OpenAI when reporting a tool call result. * - * `content` preserves the historical plain-string payload so downstream - * integrations (tests, logging, etc.) can keep treating tool output as - * `String`. When an MCP server returns richer data we additionally populate - * `content_items` with the structured form that the Responses API understands. + * `body` serializes directly as the wire value for `function_call_output.output`. + * `success` remains internal metadata for downstream handling. */ -export type FunctionCallOutputPayload = { content: string, content_items: Array | null, success: boolean | null, }; +export type FunctionCallOutputPayload = { body: FunctionCallOutputBody, success: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 7d3ecb818e..a6ff6fbaf1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -69,6 +69,7 @@ export type { FileChange } from "./FileChange"; export type { ForcedLoginMethod } from "./ForcedLoginMethod"; export type { ForkConversationParams } from "./ForkConversationParams"; export type { ForkConversationResponse } from "./ForkConversationResponse"; +export type { FunctionCallOutputBody } from "./FunctionCallOutputBody"; export type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem"; export type { FunctionCallOutputPayload } from "./FunctionCallOutputPayload"; export type { FuzzyFileSearchParams } from "./FuzzyFileSearchParams"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallOutputContentItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallOutputContentItem.ts new file mode 100644 index 0000000000..8f432109d1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallOutputContentItem.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 DynamicToolCallOutputContentItem = { "type": "inputText", text: string, } | { "type": "inputImage", imageUrl: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts index a35b9b394a..788e6242dc 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/DynamicToolCallResponse.ts @@ -1,5 +1,6 @@ // 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 { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; -export type DynamicToolCallResponse = { output: string, success: boolean, }; +export type DynamicToolCallResponse = { contentItems: Array, success: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index ed4f74d4ad..ec5d54cba1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -46,6 +46,7 @@ export type { ConfigWriteResponse } from "./ConfigWriteResponse"; export type { ContextCompactedNotification } from "./ContextCompactedNotification"; export type { CreditsSnapshot } from "./CreditsSnapshot"; export type { DeprecationNoticeNotification } from "./DeprecationNoticeNotification"; +export type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputContentItem"; export type { DynamicToolCallParams } from "./DynamicToolCallParams"; export type { DynamicToolCallResponse } from "./DynamicToolCallResponse"; export type { DynamicToolSpec } from "./DynamicToolSpec"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 8e7beccf79..baafa84677 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2826,10 +2826,34 @@ pub struct DynamicToolCallParams { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct DynamicToolCallResponse { - pub output: String, + pub content_items: Vec, pub success: bool, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(tag = "type")] +#[ts(export_to = "v2/")] +pub enum DynamicToolCallOutputContentItem { + #[serde(rename_all = "camelCase")] + InputText { text: String }, + #[serde(rename_all = "camelCase")] + InputImage { image_url: String }, +} + +impl From + for codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem +{ + fn from(item: DynamicToolCallOutputContentItem) -> Self { + match item { + DynamicToolCallOutputContentItem::InputText { text } => Self::InputText { text }, + DynamicToolCallOutputContentItem::InputImage { image_url } => { + Self::InputImage { image_url } + } + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -3189,4 +3213,61 @@ mod tests { }) ); } + + #[test] + fn dynamic_tool_response_serializes_content_items() { + let value = serde_json::to_value(DynamicToolCallResponse { + content_items: vec![DynamicToolCallOutputContentItem::InputText { + text: "dynamic-ok".to_string(), + }], + success: true, + }) + .unwrap(); + + assert_eq!( + value, + json!({ + "contentItems": [ + { + "type": "inputText", + "text": "dynamic-ok" + } + ], + "success": true, + }) + ); + } + + #[test] + fn dynamic_tool_response_serializes_text_and_image_content_items() { + let value = serde_json::to_value(DynamicToolCallResponse { + content_items: vec![ + DynamicToolCallOutputContentItem::InputText { + text: "dynamic-ok".to_string(), + }, + DynamicToolCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + ], + success: true, + }) + .unwrap(); + + assert_eq!( + value, + json!({ + "contentItems": [ + { + "type": "inputText", + "text": "dynamic-ok" + }, + { + "type": "inputImage", + "imageUrl": "data:image/png;base64,AAA" + } + ], + "success": true, + }) + ); + } } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 69a7524717..0fbb6649a2 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -122,6 +122,7 @@ Start a fresh thread when you need a new Codex conversation. "approvalPolicy": "never", "sandbox": "workspaceWrite", "personality": "friendly", + // Experimental: requires opt-in "dynamicTools": [ { "name": "lookup_ticket", @@ -556,6 +557,41 @@ Order of messages: UI guidance for IDEs: surface an approval dialog as soon as the request arrives. The turn will proceed after the server receives a response to the approval request. The terminal `item/completed` notification will be sent with the appropriate status. +### Dynamic tool calls (experimental) + +`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request/response flow are experimental APIs. To enable them, set `initialize.params.capabilities.experimentalApi = true`. + +When a dynamic tool is invoked during a turn, the server sends an `item/tool/call` JSON-RPC request to the client: + +```json +{ + "method": "item/tool/call", + "id": 60, + "params": { + "threadId": "thr_123", + "turnId": "turn_123", + "callId": "call_123", + "tool": "lookup_ticket", + "arguments": { "id": "ABC-123" } + } +} +``` + +The client must respond with content items. Use `inputText` for text and `inputImage` for image URLs/data URLs: + +```json +{ + "id": 60, + "result": { + "contentItems": [ + { "type": "inputText", "text": "Ticket ABC-123 is open." }, + { "type": "inputImage", "imageUrl": "data:image/png;base64,AAA" } + ], + "success": true + } +} +``` + ## Skills Invoke a skill by including `$` in the text input. Add a `skill` input item (recommended) so the backend injects full skill instructions instead of relying on the model to resolve the name. diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 55b185e5a8..fd8db9cb62 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -88,6 +88,7 @@ use codex_core::protocol::TurnDiffEvent; use codex_core::review_format::format_review_findings_block; use codex_core::review_prompts; use codex_protocol::ThreadId; +use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem; use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::ReviewOutputEvent; @@ -351,8 +352,9 @@ pub(crate) async fn apply_bespoke_event_handling( .submit(Op::DynamicToolResponse { id: call_id.clone(), response: CoreDynamicToolResponse { - call_id, - output: "dynamic tool calls require api v2".to_string(), + content_items: vec![CoreDynamicToolCallOutputContentItem::InputText { + text: "dynamic tool calls require api v2".to_string(), + }], success: false, }, }) diff --git a/codex-rs/app-server/src/dynamic_tools.rs b/codex-rs/app-server/src/dynamic_tools.rs index a1b424d0ee..ed284452b4 100644 --- a/codex-rs/app-server/src/dynamic_tools.rs +++ b/codex-rs/app-server/src/dynamic_tools.rs @@ -1,5 +1,6 @@ use codex_app_server_protocol::DynamicToolCallResponse; use codex_core::CodexThread; +use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem; use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse; use codex_protocol::protocol::Op; use std::sync::Arc; @@ -17,8 +18,9 @@ pub(crate) async fn on_call_response( Err(err) => { error!("request failed: {err:?}"); let fallback = CoreDynamicToolResponse { - call_id: call_id.clone(), - output: "dynamic tool request failed".to_string(), + content_items: vec![CoreDynamicToolCallOutputContentItem::InputText { + text: "dynamic tool request failed".to_string(), + }], success: false, }; if let Err(err) = conversation @@ -37,14 +39,25 @@ pub(crate) async fn on_call_response( let response = serde_json::from_value::(value).unwrap_or_else(|err| { error!("failed to deserialize DynamicToolCallResponse: {err}"); DynamicToolCallResponse { - output: "dynamic tool response was invalid".to_string(), + content_items: vec![ + codex_app_server_protocol::DynamicToolCallOutputContentItem::InputText { + text: "dynamic tool response was invalid".to_string(), + }, + ], success: false, } }); + + let DynamicToolCallResponse { + content_items, + success, + } = response; let response = CoreDynamicToolResponse { - call_id: call_id.clone(), - output: response.output, - success: response.success, + content_items: content_items + .into_iter() + .map(CoreDynamicToolCallOutputContentItem::from) + .collect(), + success, }; if let Err(err) = conversation .submit(Op::DynamicToolResponse { diff --git a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs index dc985ac49f..f4cd85a61d 100644 --- a/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs +++ b/codex-rs/app-server/tests/suite/v2/dynamic_tools.rs @@ -4,6 +4,7 @@ use app_test_support::McpProcess; use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_responses_server_sequence_unchecked; use app_test_support::to_response; +use codex_app_server_protocol::DynamicToolCallOutputContentItem; use codex_app_server_protocol::DynamicToolCallParams; use codex_app_server_protocol::DynamicToolCallResponse; use codex_app_server_protocol::DynamicToolSpec; @@ -15,6 +16,9 @@ use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::UserInput as V2UserInput; +use codex_protocol::models::FunctionCallOutputBody; +use codex_protocol::models::FunctionCallOutputContentItem; +use codex_protocol::models::FunctionCallOutputPayload; use core_test_support::responses; use pretty_assertions::assert_eq; use serde_json::Value; @@ -111,7 +115,7 @@ async fn thread_start_injects_dynamic_tools_into_model_requests() -> Result<()> /// Exercises the full dynamic tool call path (server request, client response, model output). #[tokio::test] -async fn dynamic_tool_call_round_trip_sends_output_to_model() -> Result<()> { +async fn dynamic_tool_call_round_trip_sends_text_content_items_to_model() -> Result<()> { let call_id = "dyn-call-1"; let tool_name = "demo_tool"; let tool_args = json!({ "city": "Paris" }); @@ -200,7 +204,9 @@ async fn dynamic_tool_call_round_trip_sends_output_to_model() -> Result<()> { // Respond to the tool call so the model receives a function_call_output. let response = DynamicToolCallResponse { - output: "dynamic-ok".to_string(), + content_items: vec![DynamicToolCallOutputContentItem::InputText { + text: "dynamic-ok".to_string(), + }], success: true, }; mcp.send_response(request_id, serde_json::to_value(response)?) @@ -213,11 +219,171 @@ async fn dynamic_tool_call_round_trip_sends_output_to_model() -> Result<()> { .await??; let bodies = responses_bodies(&server).await?; - let output = bodies + let payload = bodies .iter() - .find_map(|body| function_call_output_text(body, call_id)) + .find_map(|body| function_call_output_payload(body, call_id)) .context("expected function_call_output in follow-up request")?; - assert_eq!(output, "dynamic-ok"); + let expected_payload = FunctionCallOutputPayload::from_content_items(vec![ + FunctionCallOutputContentItem::InputText { + text: "dynamic-ok".to_string(), + }, + ]); + assert_eq!(payload, expected_payload); + + Ok(()) +} + +/// Ensures dynamic tool call responses can include structured content items. +#[tokio::test] +async fn dynamic_tool_call_round_trip_sends_content_items_to_model() -> Result<()> { + let call_id = "dyn-call-items-1"; + let tool_name = "demo_tool"; + let tool_args = json!({ "city": "Paris" }); + let tool_call_arguments = serde_json::to_string(&tool_args)?; + + let responses = vec![ + responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_function_call(call_id, tool_name, &tool_call_arguments), + responses::ev_completed("resp-1"), + ]), + create_final_assistant_message_sse_response("Done")?, + ]; + let server = create_mock_responses_server_sequence_unchecked(responses).await; + + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let dynamic_tool = DynamicToolSpec { + name: tool_name.to_string(), + description: "Demo dynamic tool".to_string(), + input_schema: json!({ + "type": "object", + "properties": { + "city": { "type": "string" } + }, + "required": ["city"], + "additionalProperties": false, + }), + }; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + dynamic_tools: Some(vec![dynamic_tool]), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id.clone(), + input: vec![V2UserInput::Text { + text: "Run the tool".to_string(), + text_elements: Vec::new(), + }], + ..Default::default() + }) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + let TurnStartResponse { turn } = to_response::(turn_resp)?; + + let request = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_request_message(), + ) + .await??; + let (request_id, params) = match request { + ServerRequest::DynamicToolCall { request_id, params } => (request_id, params), + other => panic!("expected DynamicToolCall request, got {other:?}"), + }; + + let expected = DynamicToolCallParams { + thread_id: thread.id, + turn_id: turn.id, + call_id: call_id.to_string(), + tool: tool_name.to_string(), + arguments: tool_args, + }; + assert_eq!(params, expected); + + let response_content_items = vec![ + DynamicToolCallOutputContentItem::InputText { + text: "dynamic-ok".to_string(), + }, + DynamicToolCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + ]; + let content_items = response_content_items + .clone() + .into_iter() + .map(|item| match item { + DynamicToolCallOutputContentItem::InputText { text } => { + FunctionCallOutputContentItem::InputText { text } + } + DynamicToolCallOutputContentItem::InputImage { image_url } => { + FunctionCallOutputContentItem::InputImage { image_url } + } + }) + .collect::>(); + let response = DynamicToolCallResponse { + content_items: response_content_items, + success: true, + }; + mcp.send_response(request_id, serde_json::to_value(response)?) + .await?; + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let bodies = responses_bodies(&server).await?; + let output_value = bodies + .iter() + .find_map(|body| function_call_output_raw_output(body, call_id)) + .context("expected function_call_output output in follow-up request")?; + assert_eq!( + output_value, + json!([ + { + "type": "input_text", + "text": "dynamic-ok" + }, + { + "type": "input_image", + "image_url": "data:image/png;base64,AAA" + } + ]) + ); + + let payload = bodies + .iter() + .find_map(|body| function_call_output_payload(body, call_id)) + .context("expected function_call_output in follow-up request")?; + assert_eq!( + payload.body, + FunctionCallOutputBody::ContentItems(content_items.clone()) + ); + assert_eq!(payload.success, None); + assert_eq!( + serde_json::to_string(&payload)?, + serde_json::to_string(&content_items)? + ); Ok(()) } @@ -248,7 +414,12 @@ fn find_tool<'a>(body: &'a Value, name: &str) -> Option<&'a Value> { }) } -fn function_call_output_text(body: &Value, call_id: &str) -> Option { +fn function_call_output_payload(body: &Value, call_id: &str) -> Option { + function_call_output_raw_output(body, call_id) + .and_then(|output| serde_json::from_value(output).ok()) +} + +fn function_call_output_raw_output(body: &Value, call_id: &str) -> Option { body.get("input") .and_then(Value::as_array) .and_then(|items| { @@ -258,8 +429,7 @@ fn function_call_output_text(body: &Value, call_id: &str) -> Option { }) }) .and_then(|item| item.get("output")) - .and_then(Value::as_str) - .map(str::to_string) + .cloned() } fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> { diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 2614ce83ef..5a4eea8836 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -3,6 +3,7 @@ use crate::config::types::Personality; use crate::error::Result; pub use codex_api::common::ResponseEvent; use codex_protocol::models::BaseInstructions; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::ResponseItem; use futures::Stream; use serde::Deserialize; @@ -97,9 +98,11 @@ fn reserialize_shell_outputs(items: &mut [ResponseItem]) { } ResponseItem::FunctionCallOutput { call_id, output } => { if shell_call_ids.remove(call_id) - && let Some(structured) = parse_structured_shell_output(&output.content) + && let Some(structured) = output + .text_content() + .and_then(parse_structured_shell_output) { - output.content = structured + output.body = FunctionCallOutputBody::Text(structured); } } _ => {} diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 4068345c39..c522e35b7f 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -4709,6 +4709,7 @@ mod tests { use crate::tools::format_exec_output_str; use codex_protocol::ThreadId; + use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use crate::protocol::CompactedItem; @@ -5342,13 +5343,14 @@ mod tests { let got = FunctionCallOutputPayload::from(&ctr); let expected = FunctionCallOutputPayload { - content: serde_json::to_string(&json!({ - "ok": true, - "value": 42 - })) - .unwrap(), + body: FunctionCallOutputBody::Text( + serde_json::to_string(&json!({ + "ok": true, + "value": 42 + })) + .unwrap(), + ), success: Some(true), - ..Default::default() }; assert_eq!(expected, got); @@ -5385,10 +5387,10 @@ mod tests { let got = FunctionCallOutputPayload::from(&ctr); let expected = FunctionCallOutputPayload { - content: serde_json::to_string(&vec![text_block("hello"), text_block("world")]) - .unwrap(), + body: FunctionCallOutputBody::Text( + serde_json::to_string(&vec![text_block("hello"), text_block("world")]).unwrap(), + ), success: Some(true), - ..Default::default() }; assert_eq!(expected, got); @@ -5405,9 +5407,10 @@ mod tests { let got = FunctionCallOutputPayload::from(&ctr); let expected = FunctionCallOutputPayload { - content: serde_json::to_string(&json!({ "message": "bad" })).unwrap(), + body: FunctionCallOutputBody::Text( + serde_json::to_string(&json!({ "message": "bad" })).unwrap(), + ), success: Some(false), - ..Default::default() }; assert_eq!(expected, got); @@ -5424,9 +5427,10 @@ mod tests { let got = FunctionCallOutputPayload::from(&ctr); let expected = FunctionCallOutputPayload { - content: serde_json::to_string(&vec![text_block("alpha")]).unwrap(), + body: FunctionCallOutputBody::Text( + serde_json::to_string(&vec![text_block("alpha")]).unwrap(), + ), success: Some(true), - ..Default::default() }; assert_eq!(expected, got); @@ -6271,7 +6275,10 @@ mod tests { .await; let output = match resp2.expect("expected Ok result") { - ToolOutput::Function { content, .. } => content, + ToolOutput::Function { + body: FunctionCallOutputBody::Text(content), + .. + } => content, _ => panic!("unexpected tool output"), }; diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index a68c4daafe..b1bf4086da 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -10,6 +10,7 @@ use crate::truncate::truncate_function_output_items_with_policy; use crate::truncate::truncate_text; use crate::user_shell_command::is_user_shell_command_text; use codex_protocol::models::ContentItem; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseItem; @@ -134,7 +135,7 @@ impl ContextManager { match &mut self.items[index] { ResponseItem::FunctionCallOutput { output, .. } => { - let Some(content_items) = output.content_items.as_mut() else { + let Some(content_items) = output.content_items_mut() else { return false; }; let mut replaced = false; @@ -268,19 +269,23 @@ impl ContextManager { let policy_with_serialization_budget = policy * 1.2; match item { ResponseItem::FunctionCallOutput { call_id, output } => { - let truncated = - truncate_text(output.content.as_str(), policy_with_serialization_budget); - let truncated_items = output.content_items.as_ref().map(|items| { - truncate_function_output_items_with_policy( - items, - policy_with_serialization_budget, - ) - }); + let body = match &output.body { + FunctionCallOutputBody::Text(content) => FunctionCallOutputBody::Text( + truncate_text(content, policy_with_serialization_budget), + ), + FunctionCallOutputBody::ContentItems(items) => { + FunctionCallOutputBody::ContentItems( + truncate_function_output_items_with_policy( + items, + policy_with_serialization_budget, + ), + ) + } + }; ResponseItem::FunctionCallOutput { call_id: call_id.clone(), output: FunctionCallOutputPayload { - content: truncated, - content_items: truncated_items, + body, success: output.success, }, } diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index a6eba62f1a..c1801e202d 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -3,6 +3,7 @@ use crate::truncate; use crate::truncate::TruncationPolicy; use codex_git::GhostCommit; use codex_protocol::models::ContentItem; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::LocalShellAction; @@ -63,10 +64,7 @@ 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() - }, + output: FunctionCallOutputPayload::from_text(content.to_string()), } } @@ -263,10 +261,7 @@ fn remove_first_item_removes_matching_output_for_function_call() { }, ResponseItem::FunctionCallOutput { call_id: "call-1".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }, ]; let mut h = create_history_with_items(items); @@ -279,10 +274,7 @@ fn remove_first_item_removes_matching_call_for_output() { let items = vec![ ResponseItem::FunctionCallOutput { call_id: "call-2".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }, ResponseItem::FunctionCall { id: None, @@ -308,10 +300,7 @@ fn remove_last_item_removes_matching_call_for_output() { }, ResponseItem::FunctionCallOutput { call_id: "call-delete-last".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }, ]; let mut h = create_history_with_items(items); @@ -327,10 +316,11 @@ fn replace_last_turn_images_replaces_tool_output_images() { ResponseItem::FunctionCallOutput { call_id: "call-1".to_string(), output: FunctionCallOutputPayload { - content: "ok".to_string(), - content_items: Some(vec![FunctionCallOutputContentItem::InputImage { - image_url: "data:image/png;base64,AAA".to_string(), - }]), + body: FunctionCallOutputBody::ContentItems(vec![ + FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + ]), success: Some(true), }, }, @@ -346,10 +336,11 @@ fn replace_last_turn_images_replaces_tool_output_images() { ResponseItem::FunctionCallOutput { call_id: "call-1".to_string(), output: FunctionCallOutputPayload { - content: "ok".to_string(), - content_items: Some(vec![FunctionCallOutputContentItem::InputText { - text: "Invalid image".to_string(), - }]), + body: FunctionCallOutputBody::ContentItems(vec![ + FunctionCallOutputContentItem::InputText { + text: "Invalid image".to_string(), + }, + ]), success: Some(true), }, }, @@ -391,10 +382,7 @@ fn remove_first_item_handles_local_shell_pair() { }, ResponseItem::FunctionCallOutput { call_id: "call-3".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }, ]; let mut h = create_history_with_items(items); @@ -560,10 +548,7 @@ fn normalization_retains_local_shell_outputs() { }, ResponseItem::FunctionCallOutput { call_id: "shell-1".to_string(), - output: FunctionCallOutputPayload { - content: "Total output lines: 1\n\nok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("Total output lines: 1\n\nok".to_string()), }, ]; @@ -583,9 +568,8 @@ fn record_items_truncates_function_call_output_content() { let item = ResponseItem::FunctionCallOutput { call_id: "call-100".to_string(), output: FunctionCallOutputPayload { - content: long_output.clone(), + body: FunctionCallOutputBody::Text(long_output.clone()), success: Some(true), - ..Default::default() }, }; @@ -594,16 +578,15 @@ fn record_items_truncates_function_call_output_content() { assert_eq!(history.items.len(), 1); match &history.items[0] { ResponseItem::FunctionCallOutput { output, .. } => { - assert_ne!(output.content, long_output); + let content = output.text_content().unwrap_or_default(); + assert_ne!(content, long_output); assert!( - output.content.contains("tokens truncated"), - "expected token-based truncation marker, got {}", - output.content + content.contains("tokens truncated"), + "expected token-based truncation marker, got {content}" ); assert!( - output.content.contains("tokens truncated"), - "expected truncation marker, got {}", - output.content + content.contains("tokens truncated"), + "expected truncation marker, got {content}" ); } other => panic!("unexpected history item: {other:?}"), @@ -648,9 +631,8 @@ fn record_items_respects_custom_token_limit() { let item = ResponseItem::FunctionCallOutput { call_id: "call-custom-limit".to_string(), output: FunctionCallOutputPayload { - content: long_output, + body: FunctionCallOutputBody::Text(long_output), success: Some(true), - ..Default::default() }, }; @@ -660,7 +642,11 @@ fn record_items_respects_custom_token_limit() { ResponseItem::FunctionCallOutput { output, .. } => output, other => panic!("unexpected history item: {other:?}"), }; - assert!(stored.content.contains("tokens truncated")); + assert!( + stored + .text_content() + .is_some_and(|content| content.contains("tokens truncated")) + ); } fn assert_truncated_message_matches(message: &str, line: &str, expected_removed: usize) { @@ -782,10 +768,7 @@ fn normalize_adds_missing_output_for_function_call() { }, ResponseItem::FunctionCallOutput { call_id: "call-x".to_string(), - output: FunctionCallOutputPayload { - content: "aborted".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("aborted".to_string()), }, ] ); @@ -859,10 +842,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() { }, ResponseItem::FunctionCallOutput { call_id: "shell-1".to_string(), - output: FunctionCallOutputPayload { - content: "aborted".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("aborted".to_string()), }, ] ); @@ -873,10 +853,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id() { fn normalize_removes_orphan_function_call_output() { let items = vec![ResponseItem::FunctionCallOutput { call_id: "orphan-1".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }]; let mut h = create_history_with_items(items); @@ -913,10 +890,7 @@ fn normalize_mixed_inserts_and_removals() { // Orphan output that should be removed ResponseItem::FunctionCallOutput { call_id: "c2".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }, // Will get an inserted custom tool output ResponseItem::CustomToolCall { @@ -955,10 +929,7 @@ fn normalize_mixed_inserts_and_removals() { }, ResponseItem::FunctionCallOutput { call_id: "c1".to_string(), - output: FunctionCallOutputPayload { - content: "aborted".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("aborted".to_string()), }, ResponseItem::CustomToolCall { id: None, @@ -985,10 +956,7 @@ fn normalize_mixed_inserts_and_removals() { }, ResponseItem::FunctionCallOutput { call_id: "s1".to_string(), - output: FunctionCallOutputPayload { - content: "aborted".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("aborted".to_string()), }, ] ); @@ -1015,10 +983,7 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() { }, ResponseItem::FunctionCallOutput { call_id: "call-x".to_string(), - output: FunctionCallOutputPayload { - content: "aborted".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("aborted".to_string()), }, ] ); @@ -1065,10 +1030,7 @@ fn normalize_adds_missing_output_for_local_shell_call_with_id_panics_in_debug() fn normalize_removes_orphan_function_call_output_panics_in_debug() { let items = vec![ResponseItem::FunctionCallOutput { call_id: "orphan-1".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }]; let mut h = create_history_with_items(items); h.normalize_history(); @@ -1099,10 +1061,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() { }, ResponseItem::FunctionCallOutput { call_id: "c2".to_string(), - output: FunctionCallOutputPayload { - content: "ok".to_string(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".to_string()), }, ResponseItem::CustomToolCall { id: None, diff --git a/codex-rs/core/src/context_manager/normalize.rs b/codex-rs/core/src/context_manager/normalize.rs index 85e25e32aa..37e177900f 100644 --- a/codex-rs/core/src/context_manager/normalize.rs +++ b/codex-rs/core/src/context_manager/normalize.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseItem; @@ -29,7 +30,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) { ResponseItem::FunctionCallOutput { call_id: call_id.clone(), output: FunctionCallOutputPayload { - content: "aborted".to_string(), + body: FunctionCallOutputBody::Text("aborted".to_string()), ..Default::default() }, }, @@ -76,7 +77,7 @@ pub(crate) fn ensure_call_outputs_present(items: &mut Vec) { ResponseItem::FunctionCallOutput { call_id: call_id.clone(), output: FunctionCallOutputPayload { - content: "aborted".to_string(), + body: FunctionCallOutputBody::Text("aborted".to_string()), ..Default::default() }, }, diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 0c9a4a0033..737b130247 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -11,6 +11,7 @@ use crate::protocol::McpInvocation; use crate::protocol::McpToolCallBeginEvent; use crate::protocol::McpToolCallEndEvent; use codex_protocol::mcp::CallToolResult; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::protocol::AskForApproval; @@ -46,9 +47,8 @@ pub(crate) async fn handle_mcp_tool_call( return ResponseInputItem::FunctionCallOutput { call_id: call_id.clone(), output: FunctionCallOutputPayload { - content: format!("err: {e}"), + body: FunctionCallOutputBody::Text(format!("err: {e}")), success: Some(false), - ..Default::default() }, }; } diff --git a/codex-rs/core/src/stream_events_utils.rs b/codex-rs/core/src/stream_events_utils.rs index 31b4f18372..394f4b9329 100644 --- a/codex-rs/core/src/stream_events_utils.rs +++ b/codex-rs/core/src/stream_events_utils.rs @@ -14,6 +14,7 @@ use crate::parse_turn_item; use crate::proposed_plan_parser::strip_proposed_plan_blocks; use crate::tools::parallel::ToolCallRuntime; use crate::tools::router::ToolRouter; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; @@ -107,7 +108,7 @@ pub(crate) async fn handle_output_item_done( let response = ResponseInputItem::FunctionCallOutput { call_id: String::new(), output: FunctionCallOutputPayload { - content: msg.to_string(), + body: FunctionCallOutputBody::Text(msg.to_string()), ..Default::default() }, }; @@ -130,7 +131,7 @@ pub(crate) async fn handle_output_item_done( let response = ResponseInputItem::FunctionCallOutput { call_id: String::new(), output: FunctionCallOutputPayload { - content: message, + body: FunctionCallOutputBody::Text(message), ..Default::default() }, }; @@ -235,9 +236,8 @@ pub(crate) fn response_input_to_response_item(input: &ResponseInputItem) -> Opti let output = match result { Ok(call_tool_result) => FunctionCallOutputPayload::from(call_tool_result), Err(err) => FunctionCallOutputPayload { - content: err.clone(), + body: FunctionCallOutputBody::Text(err.clone()), success: Some(false), - ..Default::default() }, }; Some(ResponseItem::FunctionCallOutput { diff --git a/codex-rs/core/src/tools/context.rs b/codex-rs/core/src/tools/context.rs index f0bbb158f5..e9edd7db46 100644 --- a/codex-rs/core/src/tools/context.rs +++ b/codex-rs/core/src/tools/context.rs @@ -5,7 +5,7 @@ use crate::tools::TELEMETRY_PREVIEW_MAX_LINES; use crate::tools::TELEMETRY_PREVIEW_TRUNCATION_NOTICE; use crate::turn_diff_tracker::TurnDiffTracker; use codex_protocol::mcp::CallToolResult; -use codex_protocol::models::FunctionCallOutputContentItem; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ShellToolCallParams; @@ -58,10 +58,9 @@ impl ToolPayload { #[derive(Clone)] pub enum ToolOutput { Function { - // Plain text representation of the tool output. - content: String, - // Some tool calls such as MCP calls may return structured content that can get parsed into an array of polymorphic content items. - content_items: Option>, + // Canonical output body for function-style tools. This may be plain text + // or structured content items. + body: FunctionCallOutputBody, success: Option, }, Mcp { @@ -72,7 +71,9 @@ pub enum ToolOutput { impl ToolOutput { pub fn log_preview(&self) -> String { match self { - ToolOutput::Function { content, .. } => telemetry_preview(content), + ToolOutput::Function { body, .. } => { + telemetry_preview(&body.to_text().unwrap_or_default()) + } ToolOutput::Mcp { result } => format!("{result:?}"), } } @@ -86,27 +87,28 @@ impl ToolOutput { pub fn into_response(self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem { match self { - ToolOutput::Function { - content, - content_items, - success, - } => { + ToolOutput::Function { body, success } => { + // `custom_tool_call` is the Responses API item type for freeform + // tools (`ToolSpec::Freeform`, e.g. freeform `apply_patch`). + // Those payloads must round-trip as `custom_tool_call_output` + // with plain string output. if matches!(payload, ToolPayload::Custom { .. }) { - ResponseInputItem::CustomToolCallOutput { + // Freeform/custom tools (`custom_tool_call`) use the custom + // output wire shape and remain string-only. + return ResponseInputItem::CustomToolCallOutput { call_id: call_id.to_string(), - output: content, - } - } else { - ResponseInputItem::FunctionCallOutput { - call_id: call_id.to_string(), - output: FunctionCallOutputPayload { - content, - content_items, - success, - }, - } + output: body.to_text().unwrap_or_default(), + }; + } + + // Function-style outputs (JSON function tools, including dynamic + // tools and MCP adaptation) preserve the exact body shape. + ResponseInputItem::FunctionCallOutput { + call_id: call_id.to_string(), + output: FunctionCallOutputPayload { body, success }, } } + // Direct MCP response path for MCP tool result envelopes. ToolOutput::Mcp { result } => ResponseInputItem::McpToolCallOutput { call_id: call_id.to_string(), result, @@ -158,6 +160,7 @@ fn telemetry_preview(content: &str) -> String { #[cfg(test)] mod tests { use super::*; + use codex_protocol::models::FunctionCallOutputContentItem; use pretty_assertions::assert_eq; #[test] @@ -166,8 +169,7 @@ mod tests { input: "patch".to_string(), }; let response = ToolOutput::Function { - content: "patched".to_string(), - content_items: None, + body: FunctionCallOutputBody::Text("patched".to_string()), success: Some(true), } .into_response("call-42", &payload); @@ -187,8 +189,7 @@ mod tests { arguments: "{}".to_string(), }; let response = ToolOutput::Function { - content: "ok".to_string(), - content_items: None, + body: FunctionCallOutputBody::Text("ok".to_string()), success: Some(true), } .into_response("fn-1", &payload); @@ -196,14 +197,58 @@ mod tests { match response { ResponseInputItem::FunctionCallOutput { call_id, output } => { assert_eq!(call_id, "fn-1"); - assert_eq!(output.content, "ok"); - assert!(output.content_items.is_none()); + assert_eq!(output.text_content(), Some("ok")); + assert!(output.content_items().is_none()); assert_eq!(output.success, Some(true)); } other => panic!("expected FunctionCallOutput, got {other:?}"), } } + #[test] + fn custom_tool_calls_can_derive_text_from_content_items() { + let payload = ToolPayload::Custom { + input: "patch".to_string(), + }; + let response = ToolOutput::Function { + body: FunctionCallOutputBody::ContentItems(vec![ + FunctionCallOutputContentItem::InputText { + text: "line 1".to_string(), + }, + FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + FunctionCallOutputContentItem::InputText { + text: "line 2".to_string(), + }, + ]), + success: Some(true), + } + .into_response("call-99", &payload); + + match response { + ResponseInputItem::CustomToolCallOutput { call_id, output } => { + assert_eq!(call_id, "call-99"); + assert_eq!(output, "line 1\nline 2"); + } + other => panic!("expected CustomToolCallOutput, got {other:?}"), + } + } + + #[test] + fn log_preview_uses_content_items_when_plain_text_is_missing() { + let output = ToolOutput::Function { + body: FunctionCallOutputBody::ContentItems(vec![ + FunctionCallOutputContentItem::InputText { + text: "preview".to_string(), + }, + ]), + success: Some(true), + }; + + assert_eq!(output.log_preview(), "preview"); + } + #[test] fn telemetry_preview_returns_original_within_limits() { let content = "short output"; diff --git a/codex-rs/core/src/tools/handlers/apply_patch.rs b/codex-rs/core/src/tools/handlers/apply_patch.rs index 46723decf6..aced569ce4 100644 --- a/codex-rs/core/src/tools/handlers/apply_patch.rs +++ b/codex-rs/core/src/tools/handlers/apply_patch.rs @@ -1,3 +1,4 @@ +use codex_protocol::models::FunctionCallOutputBody; use std::collections::BTreeMap; use std::path::Path; @@ -109,8 +110,7 @@ impl ToolHandler for ApplyPatchHandler { InternalApplyPatchInvocation::Output(item) => { let content = item?; Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } @@ -155,8 +155,7 @@ impl ToolHandler for ApplyPatchHandler { ); let content = emitter.finish(event_ctx, out).await?; Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } @@ -205,8 +204,7 @@ pub(crate) async fn intercept_apply_patch( InternalApplyPatchInvocation::Output(item) => { let content = item?; Ok(Some(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), })) } @@ -242,8 +240,7 @@ pub(crate) async fn intercept_apply_patch( ToolEventCtx::new(session, turn, call_id, tracker.as_ref().copied()); let content = emitter.finish(event_ctx, out).await?; Ok(Some(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), })) } diff --git a/codex-rs/core/src/tools/handlers/collab.rs b/codex-rs/core/src/tools/handlers/collab.rs index 7ef19506d5..3110204d29 100644 --- a/codex-rs/core/src/tools/handlers/collab.rs +++ b/codex-rs/core/src/tools/handlers/collab.rs @@ -15,6 +15,7 @@ use crate::tools::registry::ToolKind; use async_trait::async_trait; use codex_protocol::ThreadId; use codex_protocol::models::BaseInstructions; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::protocol::CollabAgentInteractionBeginEvent; use codex_protocol::protocol::CollabAgentInteractionEndEvent; use codex_protocol::protocol::CollabAgentSpawnBeginEvent; @@ -184,9 +185,8 @@ mod spawn { })?; Ok(ToolOutput::Function { - content, + body: FunctionCallOutputBody::Text(content), success: Some(true), - content_items: None, }) } } @@ -273,9 +273,8 @@ mod send_input { })?; Ok(ToolOutput::Function { - content, + body: FunctionCallOutputBody::Text(content), success: Some(true), - content_items: None, }) } } @@ -441,9 +440,8 @@ mod wait { })?; Ok(ToolOutput::Function { - content, + body: FunctionCallOutputBody::Text(content), success: None, - content_items: None, }) } @@ -552,9 +550,8 @@ pub mod close_agent { })?; Ok(ToolOutput::Function { - content, + body: FunctionCallOutputBody::Text(content), success: Some(true), - content_items: None, }) } } @@ -970,7 +967,9 @@ mod tests { .await .expect("wait should succeed"); let ToolOutput::Function { - content, success, .. + body: FunctionCallOutputBody::Text(content), + success, + .. } = output else { panic!("expected function output"); @@ -1012,7 +1011,9 @@ mod tests { .await .expect("wait should succeed"); let ToolOutput::Function { - content, success, .. + body: FunctionCallOutputBody::Text(content), + success, + .. } = output else { panic!("expected function output"); @@ -1103,7 +1104,9 @@ mod tests { .await .expect("wait should succeed"); let ToolOutput::Function { - content, success, .. + body: FunctionCallOutputBody::Text(content), + success, + .. } = output else { panic!("expected function output"); @@ -1141,7 +1144,9 @@ mod tests { .await .expect("close_agent should succeed"); let ToolOutput::Function { - content, success, .. + body: FunctionCallOutputBody::Text(content), + success, + .. } = output else { panic!("expected function output"); diff --git a/codex-rs/core/src/tools/handlers/dynamic.rs b/codex-rs/core/src/tools/handlers/dynamic.rs index a68c70b98d..51dc8db56f 100644 --- a/codex-rs/core/src/tools/handlers/dynamic.rs +++ b/codex-rs/core/src/tools/handlers/dynamic.rs @@ -10,6 +10,8 @@ use crate::tools::registry::ToolKind; use async_trait::async_trait; use codex_protocol::dynamic_tools::DynamicToolCallRequest; use codex_protocol::dynamic_tools::DynamicToolResponse; +use codex_protocol::models::FunctionCallOutputBody; +use codex_protocol::models::FunctionCallOutputContentItem; use codex_protocol::protocol::EventMsg; use serde_json::Value; use tokio::sync::oneshot; @@ -55,10 +57,19 @@ impl ToolHandler for DynamicToolHandler { ) })?; + let DynamicToolResponse { + content_items, + success, + } = response; + let body = content_items + .into_iter() + .map(FunctionCallOutputContentItem::from) + .collect::>(); + let body = FunctionCallOutputBody::ContentItems(body); + Ok(ToolOutput::Function { - content: response.output, - content_items: None, - success: Some(response.success), + body, + success: Some(success), }) } } diff --git a/codex-rs/core/src/tools/handlers/grep_files.rs b/codex-rs/core/src/tools/handlers/grep_files.rs index a3e89af6a8..9fbc6c17a3 100644 --- a/codex-rs/core/src/tools/handlers/grep_files.rs +++ b/codex-rs/core/src/tools/handlers/grep_files.rs @@ -1,3 +1,4 @@ +use codex_protocol::models::FunctionCallOutputBody; use std::path::Path; use std::time::Duration; @@ -86,14 +87,12 @@ impl ToolHandler for GrepFilesHandler { if search_results.is_empty() { Ok(ToolOutput::Function { - content: "No matches found.".to_string(), - content_items: None, + body: FunctionCallOutputBody::Text("No matches found.".to_string()), success: Some(false), }) } else { Ok(ToolOutput::Function { - content: search_results.join("\n"), - content_items: None, + body: FunctionCallOutputBody::Text(search_results.join("\n")), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/list_dir.rs b/codex-rs/core/src/tools/handlers/list_dir.rs index a06fca3d14..5535ce0ba6 100644 --- a/codex-rs/core/src/tools/handlers/list_dir.rs +++ b/codex-rs/core/src/tools/handlers/list_dir.rs @@ -1,3 +1,4 @@ +use codex_protocol::models::FunctionCallOutputBody; use std::collections::VecDeque; use std::ffi::OsStr; use std::fs::FileType; @@ -102,8 +103,7 @@ impl ToolHandler for ListDirHandler { output.push(format!("Absolute path: {}", path.display())); output.extend(entries); Ok(ToolOutput::Function { - content: output.join("\n"), - content_items: None, + body: FunctionCallOutputBody::Text(output.join("\n")), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/mcp.rs b/codex-rs/core/src/tools/handlers/mcp.rs index 5138b3cd0d..8ea46b4ed8 100644 --- a/codex-rs/core/src/tools/handlers/mcp.rs +++ b/codex-rs/core/src/tools/handlers/mcp.rs @@ -8,6 +8,7 @@ use crate::tools::context::ToolOutput; use crate::tools::context::ToolPayload; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; +use codex_protocol::models::ResponseInputItem; pub struct McpHandler; @@ -53,20 +54,11 @@ impl ToolHandler for McpHandler { .await; match response { - codex_protocol::models::ResponseInputItem::McpToolCallOutput { result, .. } => { - Ok(ToolOutput::Mcp { result }) - } - codex_protocol::models::ResponseInputItem::FunctionCallOutput { output, .. } => { - let codex_protocol::models::FunctionCallOutputPayload { - content, - content_items, - success, - } = output; - Ok(ToolOutput::Function { - content, - content_items, - success, - }) + ResponseInputItem::McpToolCallOutput { result, .. } => Ok(ToolOutput::Mcp { result }), + ResponseInputItem::FunctionCallOutput { output, .. } => { + let success = output.success; + let body = output.body; + Ok(ToolOutput::Function { body, success }) } _ => Err(FunctionCallError::RespondToModel( "mcp handler received unexpected response variant".to_string(), diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index df421bd6d3..fe541197cd 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -1,3 +1,4 @@ +use codex_protocol::models::FunctionCallOutputBody; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -296,12 +297,10 @@ async fn handle_list_resources( match payload_result { Ok(payload) => match serialize_function_output(payload) { Ok(output) => { - let ToolOutput::Function { - content, success, .. - } = &output - else { + let ToolOutput::Function { body, success } = &output else { unreachable!("MCP resource handler should return function output"); }; + let content = body.to_text().unwrap_or_default(); let duration = start.elapsed(); emit_tool_call_end( &session, @@ -309,7 +308,7 @@ async fn handle_list_resources( &call_id, invocation, duration, - Ok(call_tool_result_from_content(content, *success)), + Ok(call_tool_result_from_content(&content, *success)), ) .await; Ok(output) @@ -405,12 +404,10 @@ async fn handle_list_resource_templates( match payload_result { Ok(payload) => match serialize_function_output(payload) { Ok(output) => { - let ToolOutput::Function { - content, success, .. - } = &output - else { + let ToolOutput::Function { body, success } = &output else { unreachable!("MCP resource handler should return function output"); }; + let content = body.to_text().unwrap_or_default(); let duration = start.elapsed(); emit_tool_call_end( &session, @@ -418,7 +415,7 @@ async fn handle_list_resource_templates( &call_id, invocation, duration, - Ok(call_tool_result_from_content(content, *success)), + Ok(call_tool_result_from_content(&content, *success)), ) .await; Ok(output) @@ -494,12 +491,10 @@ async fn handle_read_resource( match payload_result { Ok(payload) => match serialize_function_output(payload) { Ok(output) => { - let ToolOutput::Function { - content, success, .. - } = &output - else { + let ToolOutput::Function { body, success } = &output else { unreachable!("MCP resource handler should return function output"); }; + let content = body.to_text().unwrap_or_default(); let duration = start.elapsed(); emit_tool_call_end( &session, @@ -507,7 +502,7 @@ async fn handle_read_resource( &call_id, invocation, duration, - Ok(call_tool_result_from_content(content, *success)), + Ok(call_tool_result_from_content(&content, *success)), ) .await; Ok(output) @@ -622,8 +617,7 @@ where })?; Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index 88e0b0dc27..2b43429cc8 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -11,6 +11,7 @@ use crate::tools::registry::ToolKind; use crate::tools::spec::JsonSchema; use async_trait::async_trait; use codex_protocol::config_types::ModeKind; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::EventMsg; use std::collections::BTreeMap; @@ -88,8 +89,7 @@ impl ToolHandler for PlanHandler { handle_update_plan(session.as_ref(), turn.as_ref(), arguments, call_id).await?; Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/read_file.rs b/codex-rs/core/src/tools/handlers/read_file.rs index eb5ce647ce..59c6ced7f5 100644 --- a/codex-rs/core/src/tools/handlers/read_file.rs +++ b/codex-rs/core/src/tools/handlers/read_file.rs @@ -1,3 +1,4 @@ +use codex_protocol::models::FunctionCallOutputBody; use std::collections::VecDeque; use std::path::PathBuf; @@ -146,8 +147,7 @@ impl ToolHandler for ReadFileHandler { } }; Ok(ToolOutput::Function { - content: collected.join("\n"), - content_items: None, + body: FunctionCallOutputBody::Text(collected.join("\n")), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index 6d014755b5..6e1da5641f 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use codex_protocol::models::FunctionCallOutputBody; use crate::function_tool::FunctionCallError; use crate::tools::context::ToolInvocation; @@ -120,8 +121,7 @@ impl ToolHandler for RequestUserInputHandler { })?; Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index f62caea556..cd1aa9056a 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use codex_protocol::ThreadId; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::ShellCommandToolCallParams; use codex_protocol::models::ShellToolCallParams; use std::sync::Arc; @@ -329,8 +330,7 @@ impl ShellHandler { let event_ctx = ToolEventCtx::new(session.as_ref(), turn.as_ref(), &call_id, None); let content = emitter.finish(event_ctx, out).await?; Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/test_sync.rs b/codex-rs/core/src/tools/handlers/test_sync.rs index 643cb464fa..4d8fe1025b 100644 --- a/codex-rs/core/src/tools/handlers/test_sync.rs +++ b/codex-rs/core/src/tools/handlers/test_sync.rs @@ -1,3 +1,4 @@ +use codex_protocol::models::FunctionCallOutputBody; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::sync::Arc; @@ -91,8 +92,7 @@ impl ToolHandler for TestSyncHandler { } Ok(ToolOutput::Function { - content: "ok".to_string(), - content_items: None, + body: FunctionCallOutputBody::Text("ok".to_string()), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/unified_exec.rs b/codex-rs/core/src/tools/handlers/unified_exec.rs index 2e331bae59..1eb51dd733 100644 --- a/codex-rs/core/src/tools/handlers/unified_exec.rs +++ b/codex-rs/core/src/tools/handlers/unified_exec.rs @@ -18,6 +18,7 @@ use crate::unified_exec::UnifiedExecProcessManager; use crate::unified_exec::UnifiedExecResponse; use crate::unified_exec::WriteStdinRequest; use async_trait::async_trait; +use codex_protocol::models::FunctionCallOutputBody; use serde::Deserialize; use std::path::PathBuf; use std::sync::Arc; @@ -238,8 +239,7 @@ impl ToolHandler for UnifiedExecHandler { let content = format_response(&response); Ok(ToolOutput::Function { - content, - content_items: None, + body: FunctionCallOutputBody::Text(content), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/handlers/view_image.rs b/codex-rs/core/src/tools/handlers/view_image.rs index 87dd7207b1..fe62522180 100644 --- a/codex-rs/core/src/tools/handlers/view_image.rs +++ b/codex-rs/core/src/tools/handlers/view_image.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use codex_protocol::models::FunctionCallOutputBody; use serde::Deserialize; use tokio::fs; @@ -92,8 +93,7 @@ impl ToolHandler for ViewImageHandler { .await; Ok(ToolOutput::Function { - content: "attached local image path".to_string(), - content_items: None, + body: FunctionCallOutputBody::Text("attached local image path".to_string()), success: Some(true), }) } diff --git a/codex-rs/core/src/tools/parallel.rs b/codex-rs/core/src/tools/parallel.rs index dcd3ae40ad..ca08048bd8 100644 --- a/codex-rs/core/src/tools/parallel.rs +++ b/codex-rs/core/src/tools/parallel.rs @@ -17,6 +17,7 @@ use crate::tools::context::SharedTurnDiffTracker; use crate::tools::context::ToolPayload; use crate::tools::router::ToolCall; use crate::tools::router::ToolRouter; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; @@ -119,7 +120,7 @@ impl ToolCallRuntime { _ => ResponseInputItem::FunctionCallOutput { call_id: call.call_id.clone(), output: FunctionCallOutputPayload { - content: Self::abort_message(call, secs), + body: FunctionCallOutputBody::Text(Self::abort_message(call, secs)), ..Default::default() }, }, diff --git a/codex-rs/core/src/tools/router.rs b/codex-rs/core/src/tools/router.rs index 51328ccc9f..1eb6190bcb 100644 --- a/codex-rs/core/src/tools/router.rs +++ b/codex-rs/core/src/tools/router.rs @@ -11,6 +11,7 @@ use crate::tools::registry::ToolRegistry; use crate::tools::spec::ToolsConfig; use crate::tools::spec::build_specs; use codex_protocol::dynamic_tools::DynamicToolSpec; +use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::LocalShellAction; use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; @@ -181,9 +182,8 @@ impl ToolRouter { ResponseInputItem::FunctionCallOutput { call_id, output: codex_protocol::models::FunctionCallOutputPayload { - content: message, + body: FunctionCallOutputBody::Text(message), success: Some(false), - ..Default::default() }, } } diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 62135127f2..b6743a6830 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1314,10 +1314,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { }); prompt.input.push(ResponseItem::FunctionCallOutput { call_id: "function-call-id".into(), - output: FunctionCallOutputPayload { - content: "ok".into(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".into()), }); prompt.input.push(ResponseItem::LocalShellCall { id: Some("local-shell-id".into()), diff --git a/codex-rs/protocol/src/dynamic_tools.rs b/codex-rs/protocol/src/dynamic_tools.rs index e55d372d8e..8b5405f307 100644 --- a/codex-rs/protocol/src/dynamic_tools.rs +++ b/codex-rs/protocol/src/dynamic_tools.rs @@ -24,7 +24,16 @@ pub struct DynamicToolCallRequest { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] pub struct DynamicToolResponse { - pub call_id: String, - pub output: String, + pub content_items: Vec, pub success: bool, } + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] +#[serde(tag = "type", rename_all = "camelCase")] +#[ts(tag = "type")] +pub enum DynamicToolCallOutputContentItem { + #[serde(rename_all = "camelCase")] + InputText { text: String }, + #[serde(rename_all = "camelCase")] + InputImage { image_url: String }, +} diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 4231423a63..15114d970b 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -129,11 +129,11 @@ pub enum ResponseItem { arguments: String, call_id: String, }, - // NOTE: The input schema for `function_call_output` objects that clients send to the - // OpenAI /v1/responses endpoint is NOT the same shape as the objects the server returns on the - // SSE stream. When *sending* we must wrap the string output inside an object that includes a - // required `success` boolean. To ensure we serialize exactly the expected shape we introduce - // a dedicated payload struct and flatten it here. + // NOTE: The `output` field for `function_call_output` uses a dedicated payload type with + // custom serialization. On the wire it is either: + // - a plain string (`content`) + // - an array of structured content items (`content_items`) + // We keep this behavior centralized in `FunctionCallOutputPayload`. FunctionCallOutput { call_id: String, output: FunctionCallOutputPayload, @@ -617,9 +617,8 @@ impl From for ResponseItem { let output = match result { Ok(result) => FunctionCallOutputPayload::from(&result), Err(tool_call_err) => FunctionCallOutputPayload { - content: format!("err: {tool_call_err:?}"), + body: FunctionCallOutputBody::Text(format!("err: {tool_call_err:?}")), success: Some(false), - ..Default::default() }, }; Self::FunctionCallOutput { call_id, output } @@ -780,39 +779,146 @@ pub enum FunctionCallOutputContentItem { InputImage { image_url: String }, } +/// Converts structured function-call output content into plain text for +/// human-readable surfaces. +/// +/// This conversion is intentionally lossy: +/// - only `input_text` items are included +/// - image items are ignored +/// +/// We use this helper where callers still need a string representation (for +/// example telemetry previews or legacy string-only output paths) while keeping +/// the original multimodal `content_items` as the authoritative payload sent to +/// the model. +pub fn function_call_output_content_items_to_text( + content_items: &[FunctionCallOutputContentItem], +) -> Option { + let text_segments = content_items + .iter() + .filter_map(|item| match item { + FunctionCallOutputContentItem::InputText { text } if !text.trim().is_empty() => { + Some(text.as_str()) + } + FunctionCallOutputContentItem::InputText { .. } + | FunctionCallOutputContentItem::InputImage { .. } => None, + }) + .collect::>(); + + if text_segments.is_empty() { + None + } else { + Some(text_segments.join("\n")) + } +} + +impl From + for FunctionCallOutputContentItem +{ + fn from(item: crate::dynamic_tools::DynamicToolCallOutputContentItem) -> Self { + match item { + crate::dynamic_tools::DynamicToolCallOutputContentItem::InputText { text } => { + Self::InputText { text } + } + crate::dynamic_tools::DynamicToolCallOutputContentItem::InputImage { image_url } => { + Self::InputImage { image_url } + } + } + } +} + /// The payload we send back to OpenAI when reporting a tool call result. /// -/// `content` preserves the historical plain-string payload so downstream -/// integrations (tests, logging, etc.) can keep treating tool output as -/// `String`. When an MCP server returns richer data we additionally populate -/// `content_items` with the structured form that the Responses API understands. +/// `body` serializes directly as the wire value for `function_call_output.output`. +/// `success` remains internal metadata for downstream handling. #[derive(Debug, Default, Clone, PartialEq, JsonSchema, TS)] pub struct FunctionCallOutputPayload { - pub content: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub content_items: Option>, + pub body: FunctionCallOutputBody, pub success: Option, } -#[derive(Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)] #[serde(untagged)] -enum FunctionCallOutputPayloadSerde { +pub enum FunctionCallOutputBody { Text(String), - Items(Vec), + ContentItems(Vec), } -// The Responses API expects two *different* shapes depending on success vs failure: -// • success → output is a plain string (no nested object) -// • failure → output is an object { content, success:false } +impl FunctionCallOutputBody { + /// Best-effort conversion of a function-call output body to plain text for + /// human-readable surfaces. + /// + /// This conversion is intentionally lossy when the body contains content + /// items: image entries are dropped and text entries are joined with + /// newlines. + pub fn to_text(&self) -> Option { + match self { + Self::Text(content) => Some(content.clone()), + Self::ContentItems(items) => function_call_output_content_items_to_text(items), + } + } +} + +impl Default for FunctionCallOutputBody { + fn default() -> Self { + Self::Text(String::new()) + } +} + +impl FunctionCallOutputPayload { + pub fn from_text(content: String) -> Self { + Self { + body: FunctionCallOutputBody::Text(content), + success: None, + } + } + + pub fn from_content_items(content_items: Vec) -> Self { + Self { + body: FunctionCallOutputBody::ContentItems(content_items), + success: None, + } + } + + pub fn text_content(&self) -> Option<&str> { + match &self.body { + FunctionCallOutputBody::Text(content) => Some(content), + FunctionCallOutputBody::ContentItems(_) => None, + } + } + + pub fn text_content_mut(&mut self) -> Option<&mut String> { + match &mut self.body { + FunctionCallOutputBody::Text(content) => Some(content), + FunctionCallOutputBody::ContentItems(_) => None, + } + } + + pub fn content_items(&self) -> Option<&[FunctionCallOutputContentItem]> { + match &self.body { + FunctionCallOutputBody::Text(_) => None, + FunctionCallOutputBody::ContentItems(items) => Some(items), + } + } + + pub fn content_items_mut(&mut self) -> Option<&mut Vec> { + match &mut self.body { + FunctionCallOutputBody::Text(_) => None, + FunctionCallOutputBody::ContentItems(items) => Some(items), + } + } +} + +// `function_call_output.output` is encoded as either: +// - an array of structured content items +// - a plain string impl Serialize for FunctionCallOutputPayload { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - if let Some(items) = &self.content_items { - items.serialize(serializer) - } else { - serializer.serialize_str(&self.content) + match &self.body { + FunctionCallOutputBody::Text(content) => serializer.serialize_str(content), + FunctionCallOutputBody::ContentItems(items) => items.serialize(serializer), } } } @@ -822,20 +928,11 @@ impl<'de> Deserialize<'de> for FunctionCallOutputPayload { where D: Deserializer<'de>, { - match FunctionCallOutputPayloadSerde::deserialize(deserializer)? { - FunctionCallOutputPayloadSerde::Text(content) => Ok(FunctionCallOutputPayload { - content, - ..Default::default() - }), - FunctionCallOutputPayloadSerde::Items(items) => { - let content = serde_json::to_string(&items).map_err(serde::de::Error::custom)?; - Ok(FunctionCallOutputPayload { - content, - content_items: Some(items), - success: None, - }) - } - } + let body = FunctionCallOutputBody::deserialize(deserializer)?; + Ok(FunctionCallOutputPayload { + body, + success: None, + }) } } @@ -856,16 +953,14 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { match serde_json::to_string(structured_content) { Ok(serialized_structured_content) => { return FunctionCallOutputPayload { - content: serialized_structured_content, + body: FunctionCallOutputBody::Text(serialized_structured_content), success: Some(is_success), - ..Default::default() }; } Err(err) => { return FunctionCallOutputPayload { - content: err.to_string(), + body: FunctionCallOutputBody::Text(err.to_string()), success: Some(false), - ..Default::default() }; } } @@ -875,18 +970,21 @@ impl From<&CallToolResult> for FunctionCallOutputPayload { Ok(serialized_content) => serialized_content, Err(err) => { return FunctionCallOutputPayload { - content: err.to_string(), + body: FunctionCallOutputBody::Text(err.to_string()), success: Some(false), - ..Default::default() }; } }; let content_items = convert_mcp_content_to_items(content); + let body = match content_items { + Some(content_items) => FunctionCallOutputBody::ContentItems(content_items), + None => FunctionCallOutputBody::Text(serialized_content), + }; + FunctionCallOutputPayload { - content: serialized_content, - content_items, + body, success: Some(is_success), } } @@ -937,19 +1035,18 @@ fn convert_mcp_content_to_items( } // Implement Display so callers can treat the payload like a plain string when logging or doing -// trivial substring checks in tests (existing tests call `.contains()` on the output). Display -// returns the raw `content` field. +// trivial substring checks in tests (existing tests call `.contains()` on the output). For +// `ContentItems`, Display emits a JSON representation. impl std::fmt::Display for FunctionCallOutputPayload { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.content) - } -} - -impl std::ops::Deref for FunctionCallOutputPayload { - type Target = str; - fn deref(&self) -> &Self::Target { - &self.content + match &self.body { + FunctionCallOutputBody::Text(content) => f.write_str(content), + FunctionCallOutputBody::ContentItems(items) => { + let content = serde_json::to_string(items).unwrap_or_default(); + f.write_str(content.as_str()) + } + } } } @@ -1010,6 +1107,61 @@ mod tests { assert_eq!(convert_mcp_content_to_items(&contents), None); } + #[test] + fn function_call_output_content_items_to_text_joins_text_segments() { + let content_items = vec![ + FunctionCallOutputContentItem::InputText { + text: "line 1".to_string(), + }, + FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + FunctionCallOutputContentItem::InputText { + text: "line 2".to_string(), + }, + ]; + + let text = function_call_output_content_items_to_text(&content_items); + assert_eq!(text, Some("line 1\nline 2".to_string())); + } + + #[test] + fn function_call_output_content_items_to_text_ignores_blank_text_and_images() { + let content_items = vec![ + FunctionCallOutputContentItem::InputText { + text: " ".to_string(), + }, + FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + ]; + + let text = function_call_output_content_items_to_text(&content_items); + assert_eq!(text, None); + } + + #[test] + fn function_call_output_body_to_text_returns_plain_text_content() { + let body = FunctionCallOutputBody::Text("ok".to_string()); + let text = body.to_text(); + assert_eq!(text, Some("ok".to_string())); + } + + #[test] + fn function_call_output_body_to_text_uses_content_item_fallback() { + let body = FunctionCallOutputBody::ContentItems(vec![ + FunctionCallOutputContentItem::InputText { + text: "line 1".to_string(), + }, + FunctionCallOutputContentItem::InputImage { + image_url: "data:image/png;base64,AAA".to_string(), + }, + ]); + + let text = body.to_text(); + assert_eq!(text, Some("line 1".to_string())); + } + #[test] fn converts_sandbox_mode_into_developer_instructions() { let workspace_write: DeveloperInstructions = SandboxMode::WorkspaceWrite.into(); @@ -1156,10 +1308,7 @@ mod tests { fn serializes_success_as_plain_string() -> Result<()> { let item = ResponseInputItem::FunctionCallOutput { call_id: "call1".into(), - output: FunctionCallOutputPayload { - content: "ok".into(), - ..Default::default() - }, + output: FunctionCallOutputPayload::from_text("ok".into()), }; let json = serde_json::to_string(&item)?; @@ -1175,9 +1324,8 @@ mod tests { let item = ResponseInputItem::FunctionCallOutput { call_id: "call1".into(), output: FunctionCallOutputPayload { - content: "bad".into(), + body: FunctionCallOutputBody::Text("bad".into()), success: Some(false), - ..Default::default() }, }; @@ -1202,7 +1350,10 @@ mod tests { let payload = FunctionCallOutputPayload::from(&call_tool_result); assert_eq!(payload.success, Some(true)); - let items = payload.content_items.clone().expect("content items"); + let Some(items) = payload.content_items() else { + panic!("expected content items"); + }; + let items = items.to_vec(); assert_eq!( items, vec![ @@ -1243,9 +1394,10 @@ mod tests { }; let payload = FunctionCallOutputPayload::from(&call_tool_result); - let Some(items) = payload.content_items else { + let Some(items) = payload.content_items() else { panic!("expected content items"); }; + let items = items.to_vec(); assert_eq!( items, vec![FunctionCallOutputContentItem::InputImage { @@ -1274,10 +1426,14 @@ mod tests { image_url: "data:image/png;base64,XYZ".into(), }, ]; - assert_eq!(payload.content_items, Some(expected_items.clone())); - - let expected_content = serde_json::to_string(&expected_items)?; - assert_eq!(payload.content, expected_content); + assert_eq!( + payload.body, + FunctionCallOutputBody::ContentItems(expected_items.clone()) + ); + assert_eq!( + serde_json::to_string(&payload)?, + serde_json::to_string(&expected_items)? + ); Ok(()) } From 224c9f768d60bd0bb6ee31e9c21ee1ae5736e31c Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Wed, 4 Feb 2026 16:19:13 -0800 Subject: [PATCH 37/90] chore(app-server): document experimental API opt-in (#10667) Add a section on how to opt in to the experimental API. --- codex-rs/app-server/README.md | 64 +++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 0fbb6649a2..4df8399e88 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -15,7 +15,7 @@ - [Skills](#skills) - [Apps](#apps) - [Auth endpoints](#auth-endpoints) -- [Adding an experimental field](#adding-an-experimental-field) +- [Experimental API Opt-in](#experimental-api-opt-in) ## Protocol @@ -827,7 +827,67 @@ Field notes: - `windowDurationMins` is the quota window length. - `resetsAt` is a Unix timestamp (seconds) for the next reset. -## Adding an experimental field +## Experimental API Opt-in + +Some app-server methods and fields are intentionally gated behind an experimental capability with no backwards-compatible guarantees. This lets clients choose between: + +- Stable surface only (default): no opt-in, no experimental methods/fields exposed. +- Experimental surface: opt in during `initialize`. + +### Generating stable vs experimental client schemas + +`codex app-server` schema generation defaults to the stable API surface (experimental fields and methods filtered out). Pass `--experimental` to include experimental methods/fields in generated TypeScript or JSON schema: + +```bash +# Stable-only output (default) +codex app-server generate-ts --out DIR +codex app-server generate-json-schema --out DIR + +# Include experimental API surface +codex app-server generate-ts --out DIR --experimental +codex app-server generate-json-schema --out DIR --experimental +``` + +### How clients opt in at runtime + +Set `capabilities.experimentalApi` to `true` in your single `initialize` request: + +```json +{ + "method": "initialize", + "id": 1, + "params": { + "clientInfo": { + "name": "my_client", + "title": "My Client", + "version": "0.1.0" + }, + "capabilities": { + "experimentalApi": true + } + } +} +``` + +Then send the standard `initialized` notification and proceed normally. + +Notes: + +- If `capabilities` is omitted, `experimentalApi` is treated as `false`. +- This setting is negotiated once at initialization time for the process lifetime (re-initializing is rejected with `"Already initialized"`). + +### What happens without opt-in + +If a request uses an experimental method or sets an experimental field without opting in, app-server rejects it with a JSON-RPC error. The message is: + +` requires experimentalApi capability` + +Examples of descriptor strings: + +- `mock/experimentalMethod` (method-level gate) +- `thread/start.mockExperimentalField` (field-level gate) + +### For maintainers: Adding experimental fields and methods Use this checklist when introducing a field/method that should only be available when the client opts into experimental APIs. At runtime, clients must send `initialize` with `capabilities.experimentalApi = true` to use experimental methods or fields. From 0e8d359da900512129750cd6cce0a7799ddea28e Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Wed, 4 Feb 2026 16:58:48 -0800 Subject: [PATCH 38/90] Session-level model client (#10664) Make ModelClient a session-scoped object. Move state that is session level onto the client, and make state that is per-turn explicit on corresponding methods. Stop taking a huge Config object, instead only pass in values that are actually needed. --------- Co-authored-by: Josh McKinney --- codex-rs/core/src/client.rs | 473 +++++++++++------- codex-rs/core/src/codex.rs | 138 +++-- codex-rs/core/src/compact.rs | 35 +- codex-rs/core/src/compact_remote.rs | 11 +- codex-rs/core/src/lib.rs | 2 - codex-rs/core/src/memory_trace.rs | 13 +- codex-rs/core/src/state/service.rs | 5 +- codex-rs/core/src/transport_manager.rs | 22 - codex-rs/core/tests/responses_headers.rs | 100 ++-- codex-rs/core/tests/suite/client.rs | 32 +- .../core/tests/suite/client_websockets.rs | 101 ++-- .../core/tests/suite/stream_no_completed.rs | 2 +- 12 files changed, 601 insertions(+), 333 deletions(-) delete mode 100644 codex-rs/core/src/transport_manager.rs diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 0a0f702b17..570cdc830b 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -1,5 +1,21 @@ +//! Session- and turn-scoped helpers for talking to model provider APIs. +//! +//! `ModelClient` is intended to live for the lifetime of a Codex session and holds the stable +//! configuration and state needed to talk to a provider (auth, provider selection, conversation id, +//! and feature-gated request behavior). +//! +//! Per-turn settings (model selection, reasoning controls, telemetry context, web search +//! eligibility, and turn metadata) are passed explicitly to streaming and unary methods so that the +//! turn lifetime is visible at the call site. +//! +//! A [`ModelClientSession`] is created per turn and is used to stream one or more Responses API +//! requests during that turn. It caches a Responses WebSocket connection (opened lazily) and +//! stores per-turn state such as the `x-codex-turn-state` token used for sticky routing. + use std::sync::Arc; use std::sync::OnceLock; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use crate::api_bridge::CoreAuthProvider; use crate::api_bridge::auth_provider_from_auth; @@ -33,7 +49,7 @@ use codex_otel::OtelManager; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; -use codex_protocol::config_types::WebSearchMode; +use codex_protocol::config_types::Verbosity as VerbosityConfig; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ModelInfo; use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; @@ -58,17 +74,13 @@ use crate::auth::RefreshTokenError; use crate::client_common::Prompt; use crate::client_common::ResponseEvent; use crate::client_common::ResponseStream; -use crate::config::Config; use crate::default_client::build_reqwest_client; use crate::error::CodexErr; use crate::error::Result; -use crate::features::FEATURES; -use crate::features::Feature; use crate::flags::CODEX_RS_SSE_FIXTURE; use crate::model_provider_info::ModelProviderInfo; use crate::model_provider_info::WireApi; use crate::tools::spec::create_tools_json_for_responses_api; -use crate::transport_manager::TransportManager; pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; @@ -76,31 +88,60 @@ pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = "x-responsesapi-include-timing-metrics"; +/// Session-scoped state shared by all [`ModelClient`] clones. +/// +/// This is intentionally kept minimal so `ModelClient` does not need to hold a full `Config`. Most +/// configuration is per turn and is passed explicitly to streaming/unary methods. #[derive(Debug)] struct ModelClientState { - config: Arc, auth_manager: Option>, - model_info: ModelInfo, - otel_manager: OtelManager, - provider: ModelProviderInfo, conversation_id: ThreadId, - effort: Option, - summary: ReasoningSummaryConfig, + provider: ModelProviderInfo, session_source: SessionSource, - transport_manager: TransportManager, + model_verbosity: Option, + enable_responses_websockets: bool, + enable_request_compression: bool, + include_timing_metrics: bool, + beta_features_header: Option, + disable_websockets: AtomicBool, } +/// A session-scoped client for model-provider API calls. +/// +/// This holds configuration and state that should be shared across turns within a Codex session +/// (auth, provider selection, conversation id, feature-gated request behavior, and transport +/// fallback state). +/// +/// WebSocket fallback is session-scoped: once a turn activates the HTTP fallback, subsequent turns +/// will also use HTTP for the remainder of the session. +/// +/// Turn-scoped settings (model selection, reasoning controls, telemetry context, web search +/// eligibility, and turn metadata) are passed explicitly to the relevant methods to keep turn +/// lifetime visible at the call site. +/// +/// This type is cheap to clone. #[derive(Debug, Clone)] pub struct ModelClient { state: Arc, } +/// A turn-scoped streaming session created from a [`ModelClient`]. +/// +/// The session lazily establishes a Responses WebSocket connection (and reuses it across multiple +/// requests) and caches per-turn state: +/// +/// - The last request's input items, so subsequent calls can use `response.append` when the input +/// is an incremental extension of the previous request. +/// - The `x-codex-turn-state` sticky-routing token, which must be replayed for all requests within +/// the same turn. +/// +/// Create a fresh `ModelClientSession` for each Codex turn. Reusing it across turns would replay +/// the previous turn's sticky-routing token into the next turn, which violates the client/server +/// contract and can cause routing bugs. pub struct ModelClientSession { - state: Arc, + client: ModelClient, connection: Option, websocket_last_items: Vec, - transport_manager: TransportManager, - turn_metadata_header: Option, /// Turn state for sticky routing. /// /// This is an `OnceLock` that stores the turn state value received from the server @@ -114,54 +155,65 @@ pub struct ModelClientSession { turn_state: Arc>, } -#[allow(clippy::too_many_arguments)] impl ModelClient { + #[allow(clippy::too_many_arguments)] + /// Creates a new session-scoped `ModelClient`. + /// + /// All arguments are expected to be stable for the lifetime of a Codex session. Per-turn values + /// are passed to [`ModelClientSession::stream`] (and other turn-scoped methods) explicitly. pub fn new( - config: Arc, auth_manager: Option>, - model_info: ModelInfo, - otel_manager: OtelManager, - provider: ModelProviderInfo, - effort: Option, - summary: ReasoningSummaryConfig, conversation_id: ThreadId, + provider: ModelProviderInfo, session_source: SessionSource, - transport_manager: TransportManager, + model_verbosity: Option, + enable_responses_websockets: bool, + enable_request_compression: bool, + include_timing_metrics: bool, + beta_features_header: Option, ) -> Self { Self { state: Arc::new(ModelClientState { - config, auth_manager, - model_info, - otel_manager, - provider, conversation_id, - effort, - summary, + provider, session_source, - transport_manager, + model_verbosity, + enable_responses_websockets, + enable_request_compression, + include_timing_metrics, + beta_features_header, + disable_websockets: AtomicBool::new(false), }), } } - pub fn new_session(&self, turn_metadata_header: Option) -> ModelClientSession { + /// Creates a fresh turn-scoped streaming session. + /// + /// This does not open any network connections; the WebSocket connection is established lazily + /// when the first WebSocket stream request is issued. + pub fn new_session(&self) -> ModelClientSession { ModelClientSession { - state: Arc::clone(&self.state), + client: self.clone(), connection: None, websocket_last_items: Vec::new(), - transport_manager: self.state.transport_manager.clone(), - turn_metadata_header, turn_state: Arc::new(OnceLock::new()), } } -} -impl ModelClient { /// Compacts the current conversation history using the Compact endpoint. /// /// This is a unary call (no streaming) that returns a new list of /// `ResponseItem`s representing the compacted transcript. - pub async fn compact_conversation_history(&self, prompt: &Prompt) -> Result> { + /// + /// The model selection and telemetry context are passed explicitly to keep `ModelClient` + /// session-scoped. + pub async fn compact_conversation_history( + &self, + prompt: &Prompt, + model_info: &ModelInfo, + otel_manager: &OtelManager, + ) -> Result> { if prompt.input.is_empty() { return Ok(Vec::new()); } @@ -176,13 +228,13 @@ impl ModelClient { .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); - let request_telemetry = self.build_request_telemetry(); + let request_telemetry = Self::build_request_telemetry(otel_manager); let client = ApiCompactClient::new(transport, api_provider, api_auth) .with_telemetry(Some(request_telemetry)); let instructions = prompt.base_instructions.text.clone(); let payload = ApiCompactionInput { - model: &self.state.model_info.slug, + model: &model_info.slug, input: &prompt.input, instructions: &instructions, }; @@ -197,9 +249,15 @@ impl ModelClient { /// Builds memory summaries for each provided normalized trace. /// /// This is a unary call (no streaming) to `/v1/memories/trace_summarize`. + /// + /// The model selection, reasoning effort, and telemetry context are passed explicitly to keep + /// `ModelClient` session-scoped. pub async fn summarize_memory_traces( &self, traces: Vec, + model_info: &ModelInfo, + effort: Option, + otel_manager: &OtelManager, ) -> Result> { if traces.is_empty() { return Ok(Vec::new()); @@ -216,14 +274,14 @@ impl ModelClient { .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; let api_auth = auth_provider_from_auth(auth, &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); - let request_telemetry = self.build_request_telemetry(); + let request_telemetry = Self::build_request_telemetry(otel_manager); let client = ApiMemoriesClient::new(transport, api_provider, api_auth) .with_telemetry(Some(request_telemetry)); let payload = ApiMemoryTraceSummarizeInput { - model: self.state.model_info.slug.clone(), + model: model_info.slug.clone(), traces, - reasoning: self.state.effort.map(|effort| Reasoning { + reasoning: effort.map(|effort| Reasoning { effort: Some(effort), summary: None, }), @@ -250,79 +308,62 @@ impl ModelClient { } extra_headers } + + /// Builds request telemetry for unary API calls (e.g., Compact endpoint). + fn build_request_telemetry(otel_manager: &OtelManager) -> Arc { + let telemetry = Arc::new(ApiTelemetry::new(otel_manager.clone())); + let request_telemetry: Arc = telemetry; + request_telemetry + } } impl ModelClientSession { - /// Streams a single model turn using the configured Responses transport. - pub async fn stream(&mut self, prompt: &Prompt) -> Result { - let wire_api = self.state.provider.wire_api; - match wire_api { - WireApi::Responses => { - let websocket_enabled = self.responses_websocket_enabled() - && !self.transport_manager.disable_websockets(); - - if websocket_enabled { - self.stream_responses_websocket(prompt).await - } else { - self.stream_responses_api(prompt).await - } - } - } + fn disable_websockets(&self) -> bool { + self.client.state.disable_websockets.load(Ordering::Relaxed) } - pub(crate) fn try_switch_fallback_transport(&mut self) -> bool { - let websocket_enabled = self.responses_websocket_enabled(); - let activated = self - .transport_manager - .activate_http_fallback(websocket_enabled); - if activated { - warn!("falling back to HTTP"); - self.state.otel_manager.counter( - "codex.transport.fallback_to_http", - 1, - &[("from_wire_api", "responses_websocket")], - ); - - self.connection = None; - self.websocket_last_items.clear(); - } - activated + fn activate_http_fallback(&self, websocket_enabled: bool) -> bool { + websocket_enabled + && !self + .client + .state + .disable_websockets + .swap(true, Ordering::Relaxed) } fn responses_websocket_enabled(&self) -> bool { - self.state.provider.supports_websockets - && self - .state - .config - .features - .enabled(Feature::ResponsesWebsockets) + self.client.state.provider.supports_websockets + && self.client.state.enable_responses_websockets } - fn build_responses_request(&self, prompt: &Prompt) -> Result { + fn build_responses_request(prompt: &Prompt) -> Result { let instructions = prompt.base_instructions.text.clone(); let tools_json: Vec = create_tools_json_for_responses_api(&prompt.tools)?; Ok(build_api_prompt(prompt, instructions, tools_json)) } + #[allow(clippy::too_many_arguments)] fn build_responses_options( &self, prompt: &Prompt, + model_info: &ModelInfo, + effort: Option, + summary: ReasoningSummaryConfig, + web_search_eligible: bool, + turn_metadata_header: Option<&str>, compression: Compression, ) -> ApiResponsesOptions { - let turn_metadata_header = self - .turn_metadata_header - .as_deref() - .and_then(|value| HeaderValue::from_str(value).ok()); - let model_info = &self.state.model_info; + let turn_metadata_header = + turn_metadata_header.and_then(|value| HeaderValue::from_str(value).ok()); let default_reasoning_effort = model_info.default_reasoning_level; let reasoning = if model_info.supports_reasoning_summaries { Some(Reasoning { - effort: self.state.effort.or(default_reasoning_effort), - summary: if self.state.summary == ReasoningSummaryConfig::None { + effort: effort.or(default_reasoning_effort), + summary: if summary == ReasoningSummaryConfig::None { None } else { - Some(self.state.summary) + Some(summary) }, }) } else { @@ -336,12 +377,12 @@ impl ModelClientSession { }; let verbosity = if model_info.support_verbosity { - self.state - .config + self.client + .state .model_verbosity .or(model_info.default_verbosity) } else { - if self.state.config.model_verbosity.is_some() { + if self.client.state.model_verbosity.is_some() { warn!( "model_verbosity is set but ignored as the model does not support verbosity: {}", model_info.slug @@ -351,7 +392,7 @@ impl ModelClientSession { }; let text = create_text_param_for_request(verbosity, &prompt.output_schema); - let conversation_id = self.state.conversation_id.to_string(); + let conversation_id = self.client.state.conversation_id.to_string(); ApiResponsesOptions { reasoning, @@ -360,9 +401,10 @@ impl ModelClientSession { text, store_override: None, conversation_id: Some(conversation_id), - session_source: Some(self.state.session_source.clone()), + session_source: Some(self.client.state.session_source.clone()), extra_headers: build_responses_headers( - &self.state.config, + self.client.state.beta_features_header.as_deref(), + web_search_eligible, Some(&self.turn_state), turn_metadata_header.as_ref(), ), @@ -388,6 +430,7 @@ impl ModelClientSession { fn prepare_websocket_request( &self, + model_slug: &str, api_prompt: &ApiPrompt, options: &ApiResponsesOptions, ) -> ResponsesWsRequest { @@ -408,7 +451,7 @@ impl ModelClientSession { let store = store_override.unwrap_or(false); let payload = ResponseCreateWsRequest { - model: self.state.model_info.slug.clone(), + model: model_slug.to_string(), instructions: api_prompt.instructions.clone(), input: api_prompt.input.clone(), tools: api_prompt.tools.clone(), @@ -427,6 +470,7 @@ impl ModelClientSession { async fn websocket_connection( &mut self, + otel_manager: &OtelManager, api_provider: codex_api::Provider, api_auth: CoreAuthProvider, options: &ApiResponsesOptions, @@ -439,13 +483,13 @@ impl ModelClientSession { if needs_new { let mut headers = options.extra_headers.clone(); headers.extend(build_conversation_headers(options.conversation_id.clone())); - if self.state.config.features.enabled(Feature::RuntimeMetrics) { + if self.client.state.include_timing_metrics { headers.insert( X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER, HeaderValue::from_static("true"), ); } - let websocket_telemetry = self.build_websocket_telemetry(); + let websocket_telemetry = Self::build_websocket_telemetry(otel_manager); let new_conn: ApiWebSocketConnection = ApiWebSocketResponsesClient::new(api_provider, api_auth) .connect( @@ -463,13 +507,9 @@ impl ModelClientSession { } fn responses_request_compression(&self, auth: Option<&crate::auth::CodexAuth>) -> Compression { - if self - .state - .config - .features - .enabled(Feature::EnableRequestCompression) + if self.client.state.enable_request_compression && auth.is_some_and(CodexAuth::is_chatgpt_auth) - && self.state.provider.is_openai() + && self.client.state.provider.is_openai() { Compression::Zstd } else { @@ -481,17 +521,29 @@ impl ModelClientSession { /// /// Handles SSE fixtures, reasoning summaries, verbosity, and the /// `text` controls used for output schemas. - async fn stream_responses_api(&self, prompt: &Prompt) -> Result { + #[allow(clippy::too_many_arguments)] + async fn stream_responses_api( + &self, + prompt: &Prompt, + model_info: &ModelInfo, + otel_manager: &OtelManager, + effort: Option, + summary: ReasoningSummaryConfig, + web_search_eligible: bool, + turn_metadata_header: Option<&str>, + ) -> Result { if let Some(path) = &*CODEX_RS_SSE_FIXTURE { warn!(path, "Streaming from fixture"); - let stream = - codex_api::stream_from_fixture(path, self.state.provider.stream_idle_timeout()) - .map_err(map_api_error)?; - return Ok(map_response_stream(stream, self.state.otel_manager.clone())); + let stream = codex_api::stream_from_fixture( + path, + self.client.state.provider.stream_idle_timeout(), + ) + .map_err(map_api_error)?; + return Ok(map_response_stream(stream, otel_manager.clone())); } - let auth_manager = self.state.auth_manager.clone(); - let api_prompt = self.build_responses_request(prompt)?; + let auth_manager = self.client.state.auth_manager.clone(); + let api_prompt = Self::build_responses_request(prompt)?; let mut auth_recovery = auth_manager .as_ref() @@ -502,26 +554,35 @@ impl ModelClientSession { None => None, }; let api_provider = self + .client .state .provider .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; - let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; + let api_auth = auth_provider_from_auth(auth.clone(), &self.client.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); - let (request_telemetry, sse_telemetry) = self.build_streaming_telemetry(); + let (request_telemetry, sse_telemetry) = Self::build_streaming_telemetry(otel_manager); let compression = self.responses_request_compression(auth.as_ref()); let client = ApiResponsesClient::new(transport, api_provider, api_auth) .with_telemetry(Some(request_telemetry), Some(sse_telemetry)); - let options = self.build_responses_options(prompt, compression); + let options = self.build_responses_options( + prompt, + model_info, + effort, + summary, + web_search_eligible, + turn_metadata_header, + compression, + ); let stream_result = client - .stream_prompt(&self.state.model_info.slug, &api_prompt, options) + .stream_prompt(&model_info.slug, &api_prompt, options) .await; match stream_result { Ok(stream) => { - return Ok(map_response_stream(stream, self.state.otel_manager.clone())); + return Ok(map_response_stream(stream, otel_manager.clone())); } Err(ApiError::Transport( unauthorized_transport @ TransportError::Http { status, .. }, @@ -535,9 +596,19 @@ impl ModelClientSession { } /// Streams a turn via the Responses API over WebSocket transport. - async fn stream_responses_websocket(&mut self, prompt: &Prompt) -> Result { - let auth_manager = self.state.auth_manager.clone(); - let api_prompt = self.build_responses_request(prompt)?; + #[allow(clippy::too_many_arguments)] + async fn stream_responses_websocket( + &mut self, + prompt: &Prompt, + model_info: &ModelInfo, + otel_manager: &OtelManager, + effort: Option, + summary: ReasoningSummaryConfig, + web_search_eligible: bool, + turn_metadata_header: Option<&str>, + ) -> Result { + let auth_manager = self.client.state.auth_manager.clone(); + let api_prompt = Self::build_responses_request(prompt)?; let mut auth_recovery = auth_manager .as_ref() @@ -548,17 +619,31 @@ impl ModelClientSession { None => None, }; let api_provider = self + .client .state .provider .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; - let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; + let api_auth = auth_provider_from_auth(auth.clone(), &self.client.state.provider)?; let compression = self.responses_request_compression(auth.as_ref()); - let options = self.build_responses_options(prompt, compression); - let request = self.prepare_websocket_request(&api_prompt, &options); + let options = self.build_responses_options( + prompt, + model_info, + effort, + summary, + web_search_eligible, + turn_metadata_header, + compression, + ); + let request = self.prepare_websocket_request(&model_info.slug, &api_prompt, &options); let connection = match self - .websocket_connection(api_provider.clone(), api_auth.clone(), &options) + .websocket_connection( + otel_manager, + api_provider.clone(), + api_auth.clone(), + &options, + ) .await { Ok(connection) => connection, @@ -577,35 +662,97 @@ impl ModelClientSession { .map_err(map_api_error)?; self.websocket_last_items = api_prompt.input.clone(); - return Ok(map_response_stream( - stream_result, - self.state.otel_manager.clone(), - )); + return Ok(map_response_stream(stream_result, otel_manager.clone())); } } /// Builds request and SSE telemetry for streaming API calls. - fn build_streaming_telemetry(&self) -> (Arc, Arc) { - let telemetry = Arc::new(ApiTelemetry::new(self.state.otel_manager.clone())); + fn build_streaming_telemetry( + otel_manager: &OtelManager, + ) -> (Arc, Arc) { + let telemetry = Arc::new(ApiTelemetry::new(otel_manager.clone())); let request_telemetry: Arc = telemetry.clone(); let sse_telemetry: Arc = telemetry; (request_telemetry, sse_telemetry) } /// Builds telemetry for the Responses API WebSocket transport. - fn build_websocket_telemetry(&self) -> Arc { - let telemetry = Arc::new(ApiTelemetry::new(self.state.otel_manager.clone())); + fn build_websocket_telemetry(otel_manager: &OtelManager) -> Arc { + let telemetry = Arc::new(ApiTelemetry::new(otel_manager.clone())); let websocket_telemetry: Arc = telemetry; websocket_telemetry } -} -impl ModelClient { - /// Builds request telemetry for unary API calls (e.g., Compact endpoint). - fn build_request_telemetry(&self) -> Arc { - let telemetry = Arc::new(ApiTelemetry::new(self.state.otel_manager.clone())); - let request_telemetry: Arc = telemetry; - request_telemetry + #[allow(clippy::too_many_arguments)] + /// Streams a single model request within the current turn. + /// + /// The caller is responsible for passing per-turn settings explicitly (model selection, + /// reasoning settings, telemetry context, web search eligibility, and turn metadata). This + /// method will prefer the Responses WebSocket transport when enabled and healthy, and will + /// fall back to the HTTP Responses API transport otherwise. + pub async fn stream( + &mut self, + prompt: &Prompt, + model_info: &ModelInfo, + otel_manager: &OtelManager, + effort: Option, + summary: ReasoningSummaryConfig, + web_search_eligible: bool, + turn_metadata_header: Option<&str>, + ) -> Result { + let wire_api = self.client.state.provider.wire_api; + match wire_api { + WireApi::Responses => { + let websocket_enabled = + self.responses_websocket_enabled() && !self.disable_websockets(); + + if websocket_enabled { + self.stream_responses_websocket( + prompt, + model_info, + otel_manager, + effort, + summary, + web_search_eligible, + turn_metadata_header, + ) + .await + } else { + self.stream_responses_api( + prompt, + model_info, + otel_manager, + effort, + summary, + web_search_eligible, + turn_metadata_header, + ) + .await + } + } + } + } + + /// Permanently disables WebSockets for this Codex session and resets WebSocket state. + /// + /// This is used after exhausting the provider retry budget, to force subsequent requests onto + /// the HTTP transport. Returns `true` if this call activated fallback, or `false` if fallback + /// was already active. + pub(crate) fn try_switch_fallback_transport(&mut self, otel_manager: &OtelManager) -> bool { + let websocket_enabled = self.responses_websocket_enabled(); + let activated = self.activate_http_fallback(websocket_enabled); + if activated { + warn!("falling back to HTTP"); + otel_manager.counter( + "codex.transport.fallback_to_http", + 1, + &[("from_wire_api", "responses_websocket")], + ); + + self.connection = None; + self.websocket_last_items.clear(); + } + activated } } @@ -620,44 +767,30 @@ fn build_api_prompt(prompt: &Prompt, instructions: String, tools_json: Vec ApiHeaderMap { - let enabled = FEATURES - .iter() - .filter_map(|spec| { - if spec.stage.experimental_menu_description().is_some() - && config.features.enabled(spec.id) - { - Some(spec.key) - } else { - None - } - }) - .collect::>(); - let value = enabled.join(","); - let mut headers = ApiHeaderMap::new(); - if !value.is_empty() - && let Ok(header_value) = HeaderValue::from_str(value.as_str()) - { - headers.insert("x-codex-beta-features", header_value); - } - headers -} - +/// Builds the extra headers attached to Responses API requests. +/// +/// These headers implement Codex-specific conventions: +/// +/// - `x-codex-beta-features`: comma-separated beta feature keys enabled for the session. +/// - `x-oai-web-search-eligible`: whether this turn is allowed to use web search. +/// - `x-codex-turn-state`: sticky routing token captured earlier in the turn. +/// - `x-codex-turn-metadata`: optional per-turn metadata for observability. fn build_responses_headers( - config: &Config, + beta_features_header: Option<&str>, + web_search_eligible: bool, turn_state: Option<&Arc>>, turn_metadata_header: Option<&HeaderValue>, ) -> ApiHeaderMap { - let mut headers = experimental_feature_headers(config); + let mut headers = ApiHeaderMap::new(); + if let Some(value) = beta_features_header + && !value.is_empty() + && let Ok(header_value) = HeaderValue::from_str(value) + { + headers.insert("x-codex-beta-features", header_value); + } headers.insert( WEB_SEARCH_ELIGIBLE_HEADER, - HeaderValue::from_static( - if matches!(config.web_search_mode, Some(WebSearchMode::Disabled)) { - "false" - } else { - "true" - }, - ), + HeaderValue::from_static(if web_search_eligible { "true" } else { "false" }), ); if let Some(turn_state) = turn_state && let Some(state) = turn_state.get() diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c522e35b7f..cef00f64eb 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -20,6 +20,7 @@ use crate::compact::should_use_remote_compact_task; use crate::compact_remote::run_inline_remote_auto_compact_task; use crate::connectors; use crate::exec_policy::ExecPolicyManager; +use crate::features::FEATURES; use crate::features::Feature; use crate::features::Features; use crate::features::maybe_push_unstable_features_warning; @@ -32,7 +33,6 @@ use crate::stream_events_utils::handle_non_tool_response_item; use crate::stream_events_utils::handle_output_item_done; use crate::stream_events_utils::last_assistant_message_from_item; use crate::terminal; -use crate::transport_manager::TransportManager; use crate::truncate::TruncationPolicy; use crate::turn_metadata::build_turn_metadata_header; use crate::user_notification::UserNotifier; @@ -488,7 +488,6 @@ pub(crate) struct Session { #[derive(Debug)] pub(crate) struct TurnContext { pub(crate) sub_id: String, - pub(crate) client: ModelClient, pub(crate) config: Arc, pub(crate) auth_manager: Option>, pub(crate) model_info: ModelInfo, @@ -497,7 +496,6 @@ pub(crate) struct TurnContext { pub(crate) reasoning_effort: Option, pub(crate) reasoning_summary: ReasoningSummaryConfig, pub(crate) session_source: SessionSource, - pub(crate) transport_manager: TransportManager, /// The session's current working directory. All relative paths provided by /// the model as well as sandbox policies are resolved against this path /// instead of `std::env::current_dir()`. @@ -681,6 +679,33 @@ pub(crate) struct SessionSettingsUpdate { } impl Session { + /// Builds the `x-codex-beta-features` header value for this session. + /// + /// `ModelClient` is session-scoped and intentionally does not depend on the full `Config`, so + /// we precompute the comma-separated list of enabled experimental feature keys at session + /// creation time and thread it into the client. + fn build_model_client_beta_features_header(config: &Config) -> Option { + let beta_features_header = FEATURES + .iter() + .filter_map(|spec| { + if spec.stage.experimental_menu_description().is_some() + && config.features.enabled(spec.id) + { + Some(spec.key) + } else { + None + } + }) + .collect::>() + .join(","); + + if beta_features_header.is_empty() { + None + } else { + Some(beta_features_header) + } + } + /// Don't expand the number of mutated arguments on config. We are in the process of getting rid of it. pub(crate) fn build_per_turn_config(session_configuration: &SessionConfiguration) -> Config { // todo(aibrahim): store this state somewhere else so we don't need to mut config @@ -735,9 +760,7 @@ impl Session { session_configuration: &SessionConfiguration, per_turn_config: Config, model_info: ModelInfo, - conversation_id: ThreadId, sub_id: String, - transport_manager: TransportManager, ) -> TurnContext { let reasoning_effort = session_configuration.collaboration_mode.reasoning_effort(); let reasoning_summary = session_configuration.model_reasoning_summary; @@ -746,23 +769,10 @@ impl Session { model_info.slug.as_str(), ); let session_source = session_configuration.session_source.clone(); - let auth_manager_for_context = auth_manager.clone(); - let provider_for_context = provider.clone(); - let transport_manager_for_context = transport_manager.clone(); - let otel_manager_for_context = otel_manager.clone(); + let auth_manager_for_context = auth_manager; + let provider_for_context = provider; + let otel_manager_for_context = otel_manager; let per_turn_config = Arc::new(per_turn_config); - let client = ModelClient::new( - per_turn_config.clone(), - auth_manager, - model_info.clone(), - otel_manager, - provider, - reasoning_effort, - reasoning_summary, - conversation_id, - session_source.clone(), - transport_manager, - ); let tools_config = ToolsConfig::new(&ToolsConfigParams { model_info: &model_info, @@ -773,7 +783,6 @@ impl Session { let cwd = session_configuration.cwd.clone(); TurnContext { sub_id, - client, config: per_turn_config.clone(), auth_manager: auth_manager_for_context, model_info: model_info.clone(), @@ -782,7 +791,6 @@ impl Session { reasoning_effort, reasoning_summary, session_source, - transport_manager: transport_manager_for_context, cwd, developer_instructions: session_configuration.developer_instructions.clone(), compact_prompt: session_configuration.compact_prompt.clone(), @@ -1020,7 +1028,17 @@ impl Session { file_watcher, agent_control, state_db: state_db_ctx.clone(), - transport_manager: TransportManager::new(), + model_client: ModelClient::new( + Some(Arc::clone(&auth_manager)), + conversation_id, + session_configuration.provider.clone(), + session_configuration.session_source.clone(), + config.model_verbosity, + config.features.enabled(Feature::ResponsesWebsockets), + config.features.enabled(Feature::EnableRequestCompression), + config.features.enabled(Feature::RuntimeMetrics), + Self::build_model_client_beta_features_header(config.as_ref()), + ), }; let sess = Arc::new(Session { @@ -1351,9 +1369,7 @@ impl Session { &session_configuration, per_turn_config, model_info, - self.conversation_id, sub_id, - self.services.transport_manager.clone(), ); if let Some(final_schema) = final_output_json_schema { @@ -3353,25 +3369,11 @@ async fn spawn_review_thread( let reasoning_effort = per_turn_config.model_reasoning_effort; let reasoning_summary = per_turn_config.model_reasoning_summary; let session_source = parent_turn_context.session_source.clone(); - let transport_manager = parent_turn_context.transport_manager.clone(); let per_turn_config = Arc::new(per_turn_config); - let client = ModelClient::new( - per_turn_config.clone(), - auth_manager, - model_info.clone(), - otel_manager, - provider, - reasoning_effort, - reasoning_summary, - sess.conversation_id, - session_source.clone(), - transport_manager.clone(), - ); let review_turn_context = TurnContext { sub_id: sub_id.to_string(), - client, config: per_turn_config, auth_manager: auth_manager_for_context, model_info: model_info.clone(), @@ -3380,7 +3382,6 @@ async fn spawn_review_thread( reasoning_effort, reasoning_summary, session_source, - transport_manager, tools_config, features: parent_turn_context.features.clone(), ghost_snapshot: parent_turn_context.ghost_snapshot.clone(), @@ -3605,7 +3606,9 @@ pub(crate) async fn run_turn( let turn_diff_tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())); let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; - let mut client_session = turn_context.client.new_session(turn_metadata_header); + // `ModelClientSession` is turn-scoped and caches WebSocket + sticky routing state, so we reuse + // one instance across retries within this turn. + let mut client_session = sess.services.model_client.new_session(); loop { // Note that pending_input would be something like a message the user @@ -3658,6 +3661,7 @@ pub(crate) async fn run_turn( Arc::clone(&turn_context), Arc::clone(&turn_diff_tracker), &mut client_session, + turn_metadata_header.as_deref(), sampling_request_input, tool_selection, cancellation_token.child_token(), @@ -3844,6 +3848,7 @@ struct SamplingRequestToolSelection<'a> { skill_name_counts_lower: &'a HashMap, } +#[allow(clippy::too_many_arguments)] #[instrument(level = "trace", skip_all, fields( @@ -3857,6 +3862,7 @@ async fn run_sampling_request( turn_context: Arc, turn_diff_tracker: SharedTurnDiffTracker, client_session: &mut ModelClientSession, + turn_metadata_header: Option<&str>, input: Vec, tool_selection: SamplingRequestToolSelection<'_>, cancellation_token: CancellationToken, @@ -3914,6 +3920,7 @@ async fn run_sampling_request( Arc::clone(&sess), Arc::clone(&turn_context), client_session, + turn_metadata_header, Arc::clone(&turn_diff_tracker), &prompt, cancellation_token.child_token(), @@ -3943,7 +3950,9 @@ async fn run_sampling_request( // Use the configured provider-specific stream retry budget. let max_retries = turn_context.provider.stream_max_retries(); - if retries >= max_retries && client_session.try_switch_fallback_transport() { + if retries >= max_retries + && client_session.try_switch_fallback_transport(&turn_context.otel_manager) + { sess.send_event( &turn_context, EventMsg::Warning(WarningEvent { @@ -4396,6 +4405,7 @@ async fn try_run_sampling_request( sess: Arc, turn_context: Arc, client_session: &mut ModelClientSession, + turn_metadata_header: Option<&str>, turn_diff_tracker: SharedTurnDiffTracker, prompt: &Prompt, cancellation_token: CancellationToken, @@ -4426,8 +4436,20 @@ async fn try_run_sampling_request( ); sess.persist_rollout_items(&[rollout_item]).await; + let web_search_eligible = !matches!( + turn_context.config.web_search_mode, + Some(WebSearchMode::Disabled) + ); let mut stream = client_session - .stream(prompt) + .stream( + prompt, + &turn_context.model_info, + &turn_context.otel_manager, + turn_context.reasoning_effort, + turn_context.reasoning_summary, + web_search_eligible, + turn_metadata_header, + ) .instrument(trace_span!("stream_request")) .or_cancel(&cancellation_token) .await??; @@ -5594,7 +5616,17 @@ mod tests { file_watcher, agent_control, state_db: None, - transport_manager: TransportManager::new(), + model_client: ModelClient::new( + Some(auth_manager.clone()), + conversation_id, + session_configuration.provider.clone(), + session_configuration.session_source.clone(), + config.model_verbosity, + config.features.enabled(Feature::ResponsesWebsockets), + config.features.enabled(Feature::EnableRequestCompression), + config.features.enabled(Feature::RuntimeMetrics), + Session::build_model_client_beta_features_header(config.as_ref()), + ), }; let turn_context = Session::make_turn_context( @@ -5604,9 +5636,7 @@ mod tests { &session_configuration, per_turn_config, model_info, - conversation_id, "turn_id".to_string(), - services.transport_manager.clone(), ); let session = Session { @@ -5716,7 +5746,17 @@ mod tests { file_watcher, agent_control, state_db: None, - transport_manager: TransportManager::new(), + model_client: ModelClient::new( + Some(Arc::clone(&auth_manager)), + conversation_id, + session_configuration.provider.clone(), + session_configuration.session_source.clone(), + config.model_verbosity, + config.features.enabled(Feature::ResponsesWebsockets), + config.features.enabled(Feature::EnableRequestCompression), + config.features.enabled(Feature::RuntimeMetrics), + Session::build_model_client_beta_features_header(config.as_ref()), + ), }; let turn_context = Arc::new(Session::make_turn_context( @@ -5726,9 +5766,7 @@ mod tests { &session_configuration, per_turn_config, model_info, - conversation_id, "turn_id".to_string(), - services.transport_manager.clone(), )); let session = Arc::new(Session { diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index c8aba56613..91b95759c0 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use crate::ModelProviderInfo; use crate::Prompt; +use crate::client::ModelClientSession; use crate::client_common::ResponseEvent; use crate::codex::Session; use crate::codex::TurnContext; @@ -19,6 +20,7 @@ use crate::truncate::TruncationPolicy; use crate::truncate::approx_token_count; use crate::truncate::truncate_text; use crate::util::backoff; +use codex_protocol::config_types::WebSearchMode; use codex_protocol::items::ContextCompactionItem; use codex_protocol::items::TurnItem; use codex_protocol::models::ContentItem; @@ -87,6 +89,10 @@ async fn run_compact_task_inner( let max_retries = turn_context.provider.stream_max_retries(); let mut retries = 0; + let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; + let mut client_session = sess.services.model_client.new_session(); + // Reuse one client session so turn-scoped state (sticky routing, websocket append tracking) + // survives retries within this compact turn. // TODO: If we need to guarantee the persisted mode always matches the prompt used for this // turn, capture it in TurnContext at creation time. Using SessionConfiguration here avoids @@ -119,7 +125,14 @@ async fn run_compact_task_inner( personality: turn_context.personality, ..Default::default() }; - let attempt_result = drain_to_completed(&sess, turn_context.as_ref(), &prompt).await; + let attempt_result = drain_to_completed( + &sess, + turn_context.as_ref(), + &mut client_session, + turn_metadata_header.as_deref(), + &prompt, + ) + .await; match attempt_result { Ok(()) => { @@ -335,11 +348,25 @@ fn build_compacted_history_with_limit( async fn drain_to_completed( sess: &Session, turn_context: &TurnContext, + client_session: &mut ModelClientSession, + turn_metadata_header: Option<&str>, prompt: &Prompt, ) -> CodexResult<()> { - let turn_metadata_header = turn_context.resolve_turn_metadata_header().await; - let mut client_session = turn_context.client.new_session(turn_metadata_header); - let mut stream = client_session.stream(prompt).await?; + let web_search_eligible = !matches!( + turn_context.config.web_search_mode, + Some(WebSearchMode::Disabled) + ); + let mut stream = client_session + .stream( + prompt, + &turn_context.model_info, + &turn_context.otel_manager, + turn_context.reasoning_effort, + turn_context.reasoning_summary, + web_search_eligible, + turn_metadata_header, + ) + .await?; loop { let maybe_event = stream.next().await; let Some(event) = maybe_event else { diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index fbb6863c37..f46084bd9c 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -76,9 +76,14 @@ async fn run_remote_compact_task_inner_impl( output_schema: None, }; - let mut new_history = turn_context - .client - .compact_conversation_history(&prompt) + let mut new_history = sess + .services + .model_client + .compact_conversation_history( + &prompt, + &turn_context.model_info, + &turn_context.otel_manager, + ) .await?; if !ghost_snapshots.is_empty() { diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 869df93d0a..50a1f85387 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -40,7 +40,6 @@ pub mod landlock; pub mod mcp; mod mcp_connection_manager; pub mod models_manager; -mod transport_manager; pub use mcp_connection_manager::MCP_SANDBOX_STATE_CAPABILITY; pub use mcp_connection_manager::MCP_SANDBOX_STATE_METHOD; pub use mcp_connection_manager::SandboxState; @@ -122,7 +121,6 @@ pub use rollout::list::read_head_for_summary; pub use rollout::list::read_session_meta_line; pub use rollout::rollout_date_parts; pub use rollout::session_index::find_thread_names_by_ids; -pub use transport_manager::TransportManager; mod function_tool; mod state; mod tasks; diff --git a/codex-rs/core/src/memory_trace.rs b/codex-rs/core/src/memory_trace.rs index 807ff64df1..6519980143 100644 --- a/codex-rs/core/src/memory_trace.rs +++ b/codex-rs/core/src/memory_trace.rs @@ -6,6 +6,9 @@ use crate::error::CodexErr; use crate::error::Result; use codex_api::MemoryTrace as ApiMemoryTrace; use codex_api::MemoryTraceMetadata as ApiMemoryTraceMetadata; +use codex_otel::OtelManager; +use codex_protocol::openai_models::ModelInfo; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use serde_json::Map; use serde_json::Value; @@ -27,9 +30,15 @@ struct PreparedTrace { /// /// The request/response wiring mirrors the memory trace summarize E2E flow: /// `/v1/memories/trace_summarize` with one output object per input trace. +/// +/// The caller provides the model selection, reasoning effort, and telemetry context explicitly so +/// the session-scoped [`ModelClient`] can be reused across turns. pub async fn build_memories_from_trace_files( client: &ModelClient, trace_paths: &[PathBuf], + model_info: &ModelInfo, + effort: Option, + otel_manager: &OtelManager, ) -> Result> { if trace_paths.is_empty() { return Ok(Vec::new()); @@ -41,7 +50,9 @@ pub async fn build_memories_from_trace_files( } let traces = prepared.iter().map(|trace| trace.payload.clone()).collect(); - let output = client.summarize_memory_traces(traces).await?; + let output = client + .summarize_memory_traces(traces, model_info, effort, otel_manager) + .await?; if output.len() != prepared.len() { return Err(CodexErr::InvalidRequest(format!( "unexpected memory summarize output length: expected {}, got {}", diff --git a/codex-rs/core/src/state/service.rs b/codex-rs/core/src/state/service.rs index e9a028bfd5..0e9c038038 100644 --- a/codex-rs/core/src/state/service.rs +++ b/codex-rs/core/src/state/service.rs @@ -4,6 +4,7 @@ use crate::AuthManager; use crate::RolloutRecorder; use crate::agent::AgentControl; use crate::analytics_client::AnalyticsEventsClient; +use crate::client::ModelClient; use crate::exec_policy::ExecPolicyManager; use crate::file_watcher::FileWatcher; use crate::mcp_connection_manager::McpConnectionManager; @@ -11,7 +12,6 @@ use crate::models_manager::manager::ModelsManager; use crate::skills::SkillsManager; use crate::state_db::StateDbHandle; use crate::tools::sandboxing::ApprovalStore; -use crate::transport_manager::TransportManager; use crate::unified_exec::UnifiedExecProcessManager; use crate::user_notification::UserNotifier; use codex_otel::OtelManager; @@ -37,5 +37,6 @@ pub(crate) struct SessionServices { pub(crate) file_watcher: Arc, pub(crate) agent_control: AgentControl, pub(crate) state_db: Option, - pub(crate) transport_manager: TransportManager, + /// Session-scoped model client shared across turns. + pub(crate) model_client: ModelClient, } diff --git a/codex-rs/core/src/transport_manager.rs b/codex-rs/core/src/transport_manager.rs deleted file mode 100644 index 2b5d095000..0000000000 --- a/codex-rs/core/src/transport_manager.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::sync::Arc; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; - -#[derive(Clone, Debug, Default)] -pub struct TransportManager { - disable_websockets: Arc, -} - -impl TransportManager { - pub fn new() -> Self { - Self::default() - } - - pub fn disable_websockets(&self) -> bool { - self.disable_websockets.load(Ordering::Relaxed) - } - - pub fn activate_http_fallback(&self, websocket_enabled: bool) -> bool { - websocket_enabled && !self.disable_websockets.swap(true, Ordering::Relaxed) - } -} diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index fd65bcf32a..9e70264bf9 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -10,7 +10,6 @@ use codex_core::ModelProviderInfo; use codex_core::Prompt; use codex_core::ResponseEvent; use codex_core::ResponseItem; -use codex_core::TransportManager; use codex_core::WEB_SEARCH_ELIGIBLE_HEADER; use codex_core::WireApi; use codex_core::models_manager::manager::ModelsManager; @@ -88,19 +87,19 @@ async fn responses_stream_includes_subagent_header_on_review() { session_source.clone(), ); - let mut client_session = ModelClient::new( - Arc::clone(&config), + let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled)); + let client = ModelClient::new( None, - model_info, - otel_manager, - provider, - effort, - summary, conversation_id, + provider.clone(), session_source, - TransportManager::new(), - ) - .new_session(None); + config.model_verbosity, + false, + false, + false, + None, + ); + let mut client_session = client.new_session(); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -113,7 +112,18 @@ async fn responses_stream_includes_subagent_header_on_review() { phase: None, }]; - let mut stream = client_session.stream(&prompt).await.expect("stream failed"); + let mut stream = client_session + .stream( + &prompt, + &model_info, + &otel_manager, + effort, + summary, + web_search_eligible, + None, + ) + .await + .expect("stream failed"); while let Some(event) = stream.next().await { if matches!(event, Ok(ResponseEvent::Completed { .. })) { break; @@ -188,19 +198,19 @@ async fn responses_stream_includes_subagent_header_on_other() { session_source.clone(), ); - let mut client_session = ModelClient::new( - Arc::clone(&config), + let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled)); + let client = ModelClient::new( None, - model_info, - otel_manager, - provider, - effort, - summary, conversation_id, + provider.clone(), session_source, - TransportManager::new(), - ) - .new_session(None); + config.model_verbosity, + false, + false, + false, + None, + ); + let mut client_session = client.new_session(); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -213,7 +223,18 @@ async fn responses_stream_includes_subagent_header_on_other() { phase: None, }]; - let mut stream = client_session.stream(&prompt).await.expect("stream failed"); + let mut stream = client_session + .stream( + &prompt, + &model_info, + &otel_manager, + effort, + summary, + web_search_eligible, + None, + ) + .await + .expect("stream failed"); while let Some(event) = stream.next().await { if matches!(event, Ok(ResponseEvent::Completed { .. })) { break; @@ -346,19 +367,19 @@ async fn responses_respects_model_info_overrides_from_config() { session_source.clone(), ); - let mut client = ModelClient::new( - Arc::clone(&config), + let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled)); + let client = ModelClient::new( None, - model_info, - otel_manager, - provider, - effort, - summary, conversation_id, + provider.clone(), session_source, - TransportManager::new(), - ) - .new_session(None); + config.model_verbosity, + false, + false, + false, + None, + ); + let mut client_session = client.new_session(); let mut prompt = Prompt::default(); prompt.input = vec![ResponseItem::Message { @@ -371,7 +392,18 @@ async fn responses_respects_model_info_overrides_from_config() { phase: None, }]; - let mut stream = client.stream(&prompt).await.expect("stream failed"); + let mut stream = client_session + .stream( + &prompt, + &model_info, + &otel_manager, + effort, + summary, + web_search_eligible, + None, + ) + .await + .expect("stream failed"); while let Some(event) = stream.next().await { if matches!(event, Ok(ResponseEvent::Completed { .. })) { break; diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index b6743a6830..731ce97c0d 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -11,7 +11,6 @@ use codex_core::Prompt; use codex_core::ResponseEvent; use codex_core::ResponseItem; use codex_core::ThreadManager; -use codex_core::TransportManager; use codex_core::WireApi; use codex_core::auth::AuthCredentialsStoreMode; use codex_core::built_in_model_providers; @@ -1264,19 +1263,18 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { SessionSource::Exec, ); - let mut client = ModelClient::new( - Arc::clone(&config), + let client = ModelClient::new( None, - model_info, - otel_manager, - provider, - effort, - summary, conversation_id, + provider.clone(), SessionSource::Exec, - TransportManager::new(), - ) - .new_session(None); + config.model_verbosity, + false, + false, + false, + None, + ); + let mut client_session = client.new_session(); let mut prompt = Prompt::default(); prompt.input.push(ResponseItem::Reasoning { @@ -1340,8 +1338,16 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { output: "ok".into(), }); - let mut stream = client - .stream(&prompt) + let mut stream = client_session + .stream( + &prompt, + &model_info, + &otel_manager, + effort, + summary, + true, + None, + ) .await .expect("responses stream to start"); diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 15005caa48..e3a1ef0a8e 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -8,7 +8,6 @@ use codex_core::ModelProviderInfo; use codex_core::Prompt; use codex_core::ResponseEvent; use codex_core::ResponseItem; -use codex_core::TransportManager; use codex_core::WireApi; use codex_core::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER; use codex_core::features::Feature; @@ -20,6 +19,8 @@ use codex_otel::metrics::MetricsConfig; use codex_protocol::ThreadId; use codex_protocol::account::PlanType; use codex_protocol::config_types::ReasoningSummary; +use codex_protocol::openai_models::ModelInfo; +use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig; use core_test_support::load_default_config_for_test; use core_test_support::responses::WebSocketConnectionConfig; use core_test_support::responses::WebSocketTestServer; @@ -42,6 +43,10 @@ const MODEL: &str = "gpt-5.2-codex"; struct WebsocketTestHarness { _codex_home: TempDir, client: ModelClient, + model_info: ModelInfo, + effort: Option, + summary: ReasoningSummary, + web_search_eligible: bool, otel_manager: OtelManager, } @@ -56,10 +61,10 @@ async fn responses_websocket_streams_request() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt = prompt_with_input(vec![message_item("hello")]); - stream_until_complete(&mut session, &prompt).await; + stream_until_complete(&mut client_session, &harness, &prompt).await; let connection = server.single_connection(); assert_eq!(connection.len(), 1); @@ -86,10 +91,10 @@ async fn responses_websocket_emits_websocket_telemetry_events() { let harness = websocket_harness(&server).await; harness.otel_manager.reset_runtime_metrics(); - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt = prompt_with_input(vec![message_item("hello")]); - stream_until_complete(&mut session, &prompt).await; + stream_until_complete(&mut client_session, &harness, &prompt).await; tokio::time::sleep(Duration::from_millis(10)).await; @@ -124,10 +129,10 @@ async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics let harness = websocket_harness_with_runtime_metrics(&server, true).await; harness.otel_manager.reset_runtime_metrics(); - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt = prompt_with_input(vec![message_item("hello")]); - stream_until_complete(&mut session, &prompt).await; + stream_until_complete(&mut client_session, &harness, &prompt).await; tokio::time::sleep(Duration::from_millis(10)).await; let handshake = server.single_handshake(); @@ -157,10 +162,10 @@ async fn responses_websocket_omits_timing_metrics_header_when_runtime_metrics_di .await; let harness = websocket_harness_with_runtime_metrics(&server, false).await; - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt = prompt_with_input(vec![message_item("hello")]); - stream_until_complete(&mut session, &prompt).await; + stream_until_complete(&mut client_session, &harness, &prompt).await; let handshake = server.single_handshake(); assert_eq!( @@ -182,11 +187,19 @@ async fn responses_websocket_emits_reasoning_included_event() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt = prompt_with_input(vec![message_item("hello")]); - let mut stream = session - .stream(&prompt) + let mut stream = client_session + .stream( + &prompt, + &harness.model_info, + &harness.otel_manager, + harness.effort, + harness.summary, + harness.web_search_eligible, + None, + ) .await .expect("websocket stream failed"); @@ -245,11 +258,19 @@ async fn responses_websocket_emits_rate_limit_events() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt = prompt_with_input(vec![message_item("hello")]); - let mut stream = session - .stream(&prompt) + let mut stream = client_session + .stream( + &prompt, + &harness.model_info, + &harness.otel_manager, + harness.effort, + harness.summary, + harness.web_search_eligible, + None, + ) .await .expect("websocket stream failed"); @@ -300,12 +321,12 @@ async fn responses_websocket_appends_on_prefix() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt_one = prompt_with_input(vec![message_item("hello")]); let prompt_two = prompt_with_input(vec![message_item("hello"), message_item("second")]); - stream_until_complete(&mut session, &prompt_one).await; - stream_until_complete(&mut session, &prompt_two).await; + stream_until_complete(&mut client_session, &harness, &prompt_one).await; + stream_until_complete(&mut client_session, &harness, &prompt_two).await; let connection = server.single_connection(); assert_eq!(connection.len(), 2); @@ -336,12 +357,12 @@ async fn responses_websocket_creates_on_non_prefix() { .await; let harness = websocket_harness(&server).await; - let mut session = harness.client.new_session(None); + let mut client_session = harness.client.new_session(); let prompt_one = prompt_with_input(vec![message_item("hello")]); let prompt_two = prompt_with_input(vec![message_item("different")]); - stream_until_complete(&mut session, &prompt_one).await; - stream_until_complete(&mut session, &prompt_two).await; + stream_until_complete(&mut client_session, &harness, &prompt_one).await; + stream_until_complete(&mut client_session, &harness, &prompt_two).await; let connection = server.single_connection(); assert_eq!(connection.len(), 2); @@ -431,29 +452,47 @@ async fn websocket_harness_with_runtime_metrics( SessionSource::Exec, ) .with_metrics(metrics); + let effort = None; + let summary = ReasoningSummary::Auto; + let web_search_eligible = true; let client = ModelClient::new( - Arc::clone(&config), None, - model_info, - otel_manager.clone(), - provider.clone(), - None, - ReasoningSummary::Auto, conversation_id, + provider.clone(), SessionSource::Exec, - TransportManager::new(), + config.model_verbosity, + true, + false, + runtime_metrics_enabled, + None, ); WebsocketTestHarness { _codex_home: codex_home, client, + model_info, + effort, + summary, + web_search_eligible, otel_manager, } } -async fn stream_until_complete(session: &mut ModelClientSession, prompt: &Prompt) { - let mut stream = session - .stream(prompt) +async fn stream_until_complete( + client_session: &mut ModelClientSession, + harness: &WebsocketTestHarness, + prompt: &Prompt, +) { + let mut stream = client_session + .stream( + prompt, + &harness.model_info, + &harness.otel_manager, + harness.effort, + harness.summary, + harness.web_search_eligible, + None, + ) .await .expect("websocket stream failed"); diff --git a/codex-rs/core/tests/suite/stream_no_completed.rs b/codex-rs/core/tests/suite/stream_no_completed.rs index e8b130970e..6528e6277d 100644 --- a/codex-rs/core/tests/suite/stream_no_completed.rs +++ b/codex-rs/core/tests/suite/stream_no_completed.rs @@ -71,7 +71,7 @@ async fn retries_on_early_close() { name: "openai".into(), base_url: Some(format!("{}/v1", server.uri())), // Environment variable that should exist in the test environment. - // ModelClientSession will return an error if the environment variable for the + // ModelClient will return an error if the environment variable for the // provider is not set. env_key: Some("PATH".into()), env_key_instructions: None, From cddfd1e675b90dbd2664556e4553af3881fb5ad2 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 4 Feb 2026 17:23:30 -0800 Subject: [PATCH 39/90] feat(core): add configurable log_dir (#10678) Adds a top-level `log_dir` config key (defaults to `$CODEX_HOME/log`) so one-off runs can redirect `codex-tui.log` via `-c`, e.g.: codex -c log_dir=./.codex-log Also resolves relative paths in CLI `-c/--config` overrides for `AbsolutePathBuf` values against the effective cwd (when available). Tests: - cargo test -p codex-core --- codex-rs/core/config.schema.json | 8 ++++++++ codex-rs/core/src/config/mod.rs | 26 +++++++++++++++++++++--- codex-rs/core/src/config_loader/mod.rs | 10 ++++++++- codex-rs/core/src/config_loader/tests.rs | 24 ++++++++++++++++++++++ docs/install.md | 2 +- docs/tui-stream-chunking-validation.md | 2 ++ 6 files changed, 67 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 8e9550c092..bf2439723a 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1328,6 +1328,14 @@ "description": "System instructions.", "type": "string" }, + "log_dir": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "Directory where Codex writes log files, for example `codex-tui.log`. Defaults to `$CODEX_HOME/log`." + }, "mcp_oauth_callback_port": { "description": "Optional fixed port for the local HTTP callback server used during MCP OAuth login. When unset, Codex will bind to an ephemeral port chosen by the OS.", "format": "uint16", diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index e22cfe65a1..58768e584d 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -271,6 +271,9 @@ pub struct Config { /// overridden by the `CODEX_HOME` environment variable). pub codex_home: PathBuf, + /// Directory where Codex writes log files (defaults to `$CODEX_HOME/log`). + pub log_dir: PathBuf, + /// Settings that govern if and what will be written to `~/.codex/history.jsonl`. pub history: History, @@ -896,6 +899,10 @@ pub struct ConfigToml { #[serde(default)] pub history: Option, + /// Directory where Codex writes log files, for example `codex-tui.log`. + /// Defaults to `$CODEX_HOME/log`. + pub log_dir: Option, + /// Optional URI-based file opener. If set, citations to files in the model /// output will be hyperlinked using the specified URI scheme. pub file_opener: Option, @@ -1560,6 +1567,16 @@ impl Config { let check_for_update_on_startup = cfg.check_for_update_on_startup.unwrap_or(true); + let log_dir = cfg + .log_dir + .as_ref() + .map(AbsolutePathBuf::to_path_buf) + .unwrap_or_else(|| { + let mut p = codex_home.clone(); + p.push("log"); + p + }); + // Ensure that every field of ConfigRequirements is applied to the final // Config. let ConfigRequirements { @@ -1626,6 +1643,7 @@ impl Config { tool_output_token_limit: cfg.tool_output_token_limit, agent_max_threads, codex_home, + log_dir, config_layer_stack, history, ephemeral: ephemeral.unwrap_or_default(), @@ -1816,9 +1834,7 @@ pub fn find_codex_home() -> std::io::Result { /// Returns the path to the folder where Codex logs are stored. Does not verify /// that the directory exists. pub fn log_dir(cfg: &Config) -> std::io::Result { - let mut p = cfg.codex_home.clone(); - p.push("log"); - Ok(p) + Ok(cfg.log_dir.clone()) } #[cfg(test)] @@ -3842,6 +3858,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, @@ -3927,6 +3944,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, @@ -4027,6 +4045,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, @@ -4113,6 +4132,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 28fda87357..0ae54111a4 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -144,7 +144,15 @@ pub async fn load_config_layers_state( let cli_overrides_layer = if cli_overrides.is_empty() { None } else { - Some(overrides::build_cli_overrides_layer(cli_overrides)) + let cli_overrides_layer = overrides::build_cli_overrides_layer(cli_overrides); + let base_dir = cwd + .as_ref() + .map(AbsolutePathBuf::as_path) + .unwrap_or(codex_home); + Some(resolve_relative_paths_in_config_toml( + cli_overrides_layer, + base_dir, + )?) }; // Include an entry for the "system" config folder, loading its config.toml, diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index 2d7e36d6ef..d68093c30f 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -56,6 +56,30 @@ async fn make_config_for_test( .await } +#[tokio::test] +async fn cli_overrides_resolve_relative_paths_against_cwd() -> std::io::Result<()> { + let codex_home = tempdir().expect("tempdir"); + let cwd_dir = tempdir().expect("tempdir"); + let cwd_path = cwd_dir.path().to_path_buf(); + + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .cli_overrides(vec![( + "log_dir".to_string(), + TomlValue::String("run-logs".to_string()), + )]) + .harness_overrides(ConfigOverrides { + cwd: Some(cwd_path.clone()), + ..Default::default() + }) + .build() + .await?; + + let expected = AbsolutePathBuf::resolve_path_against_base("run-logs", cwd_path)?; + assert_eq!(config.log_dir, expected.to_path_buf()); + Ok(()) +} + #[tokio::test] async fn returns_config_error_for_invalid_user_config_toml() { let tmp = tempdir().expect("tempdir"); diff --git a/docs/install.md b/docs/install.md index 20d8b54a54..ef14f5d840 100644 --- a/docs/install.md +++ b/docs/install.md @@ -51,7 +51,7 @@ cargo test --all-features Codex is written in Rust, so it honors the `RUST_LOG` environment variable to configure its logging behavior. -The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info,codex_rmcp_client=info` and log messages are written to `~/.codex/log/codex-tui.log`, so you can leave the following running in a separate terminal to monitor log messages as they are written: +The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info,codex_rmcp_client=info` and log messages are written to `~/.codex/log/codex-tui.log` by default. For a single run, you can override the log directory with `-c log_dir=...` (for example, `-c log_dir=./.codex-log`). ```bash tail -F ~/.codex/log/codex-tui.log diff --git a/docs/tui-stream-chunking-validation.md b/docs/tui-stream-chunking-validation.md index 26bd949da6..a31a7b6223 100644 --- a/docs/tui-stream-chunking-validation.md +++ b/docs/tui-stream-chunking-validation.md @@ -32,6 +32,8 @@ RUST_LOG='codex_tui::streaming::commit_tick=trace,codex_tui=info,codex_core=info ## Log capture process +Tip: for one-off measurements, run with `-c log_dir=...` to direct logs to a fresh directory and avoid mixing sessions. + 1. Record the current size of `~/.codex/log/codex-tui.log` as a start offset. 2. Run an interactive prompt that produces sustained streamed output. 3. Stop the run. From 1f47e08d6614e1494b865169d13649d977c58cb4 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Thu, 5 Feb 2026 01:52:12 +0000 Subject: [PATCH 40/90] Cloud Requirements: increase timeout and retries (#10631) Add retries and an increased-length timeout for loading Cloud Requirements. Co-authored-by: alexsong-oai --- codex-rs/cloud-requirements/src/lib.rs | 192 ++++++++++++++++++++++--- codex-rs/core/src/util.rs | 2 +- 2 files changed, 174 insertions(+), 20 deletions(-) diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 95f5180a83..9ca432dc7f 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -14,21 +14,33 @@ use codex_core::AuthManager; use codex_core::auth::CodexAuth; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigRequirementsToml; +use codex_core::util::backoff; use codex_protocol::account::PlanType; use std::sync::Arc; use std::time::Duration; use std::time::Instant; +use tokio::time::sleep; use tokio::time::timeout; const CLOUD_REQUIREMENTS_TIMEOUT: Duration = Duration::from_secs(15); +const CLOUD_REQUIREMENTS_MAX_ATTEMPTS: usize = 5; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum FetchCloudRequirementsStatus { + BackendClientInit, + Request, + Parse, +} #[async_trait] trait RequirementsFetcher: Send + Sync { - /// Returns requirements as a TOML string. + /// Returns `Ok(None)` when there are no cloud requirements for the account. /// - /// TODO(gt): For now, returns an Option. But when we want to make this fail-closed, return a - /// Result. - async fn fetch_requirements(&self, auth: &CodexAuth) -> Option; + /// Returning `Err` indicates cloud requirements could not be fetched. + async fn fetch_requirements( + &self, + auth: &CodexAuth, + ) -> Result, FetchCloudRequirementsStatus>; } struct BackendRequirementsFetcher { @@ -43,7 +55,10 @@ impl BackendRequirementsFetcher { #[async_trait] impl RequirementsFetcher for BackendRequirementsFetcher { - async fn fetch_requirements(&self, auth: &CodexAuth) -> Option { + async fn fetch_requirements( + &self, + auth: &CodexAuth, + ) -> Result, FetchCloudRequirementsStatus> { let client = BackendClient::from_auth(self.base_url.clone(), auth) .inspect_err(|err| { tracing::warn!( @@ -51,20 +66,22 @@ impl RequirementsFetcher for BackendRequirementsFetcher { "Failed to construct backend client for cloud requirements" ); }) - .ok()?; + .map_err(|_| FetchCloudRequirementsStatus::BackendClientInit)?; let response = client .get_config_requirements_file() .await .inspect_err(|err| tracing::warn!(error = %err, "Failed to fetch cloud requirements")) - .ok()?; + .map_err(|_| FetchCloudRequirementsStatus::Request)?; let Some(contents) = response.contents else { - tracing::warn!("Cloud requirements response missing contents"); - return None; + tracing::info!( + "Cloud requirements response missing contents; treating as no requirements" + ); + return Ok(None); }; - Some(contents) + Ok(Some(contents)) } } @@ -128,11 +145,41 @@ impl CloudRequirementsService { return None; } - let contents = self.fetcher.fetch_requirements(&auth).await?; - parse_cloud_requirements(&contents) - .inspect_err(|err| tracing::warn!(error = %err, "Failed to parse cloud requirements")) - .ok() - .flatten() + self.fetch_with_retries(&auth).await + } + + async fn fetch_with_retries(&self, auth: &CodexAuth) -> Option { + for attempt in 1..=CLOUD_REQUIREMENTS_MAX_ATTEMPTS { + let fetch_result = self + .fetcher + .fetch_requirements(auth) + .await + .and_then(|contents| { + contents.map_or(Ok(None), |contents| { + parse_cloud_requirements(&contents).map_err(|err| { + tracing::warn!(error = %err, "Failed to parse cloud requirements"); + FetchCloudRequirementsStatus::Parse + }) + }) + }); + + match fetch_result { + Ok(requirements) => return requirements, + Err(status) => { + if attempt < CLOUD_REQUIREMENTS_MAX_ATTEMPTS { + tracing::warn!( + status = ?status, + attempt, + max_attempts = CLOUD_REQUIREMENTS_MAX_ATTEMPTS, + "Failed to fetch cloud requirements; retrying" + ); + sleep(backoff(attempt as u64)).await; + } + } + } + } + + None } } @@ -178,8 +225,11 @@ mod tests { use codex_protocol::protocol::AskForApproval; use pretty_assertions::assert_eq; use serde_json::json; + use std::collections::VecDeque; use std::future::pending; use std::path::Path; + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering; use tempfile::tempdir; fn write_auth_json(codex_home: &Path, value: serde_json::Value) -> std::io::Result<()> { @@ -246,8 +296,11 @@ mod tests { #[async_trait::async_trait] impl RequirementsFetcher for StaticFetcher { - async fn fetch_requirements(&self, _auth: &CodexAuth) -> Option { - self.contents.clone() + async fn fetch_requirements( + &self, + _auth: &CodexAuth, + ) -> Result, FetchCloudRequirementsStatus> { + Ok(self.contents.clone()) } } @@ -255,9 +308,39 @@ mod tests { #[async_trait::async_trait] impl RequirementsFetcher for PendingFetcher { - async fn fetch_requirements(&self, _auth: &CodexAuth) -> Option { + async fn fetch_requirements( + &self, + _auth: &CodexAuth, + ) -> Result, FetchCloudRequirementsStatus> { pending::<()>().await; - None + Ok(None) + } + } + + struct SequenceFetcher { + responses: + tokio::sync::Mutex, FetchCloudRequirementsStatus>>>, + request_count: AtomicUsize, + } + + impl SequenceFetcher { + fn new(responses: Vec, FetchCloudRequirementsStatus>>) -> Self { + Self { + responses: tokio::sync::Mutex::new(VecDeque::from(responses)), + request_count: AtomicUsize::new(0), + } + } + } + + #[async_trait::async_trait] + impl RequirementsFetcher for SequenceFetcher { + async fn fetch_requirements( + &self, + _auth: &CodexAuth, + ) -> Result, FetchCloudRequirementsStatus> { + self.request_count.fetch_add(1, Ordering::SeqCst); + let mut responses = self.responses.lock().await; + responses.pop_front().unwrap_or(Ok(None)) } } @@ -359,4 +442,75 @@ mod tests { let result = handle.await.expect("cloud requirements task"); assert!(result.is_none()); } + + #[tokio::test(start_paused = true)] + async fn fetch_cloud_requirements_retries_until_success() { + let fetcher = Arc::new(SequenceFetcher::new(vec![ + Err(FetchCloudRequirementsStatus::Request), + Ok(Some("allowed_approval_policies = [\"never\"]".to_string())), + ])); + let service = CloudRequirementsService::new( + auth_manager_with_plan("business"), + fetcher.clone(), + CLOUD_REQUIREMENTS_TIMEOUT, + ); + + let handle = tokio::spawn(async move { service.fetch().await }); + tokio::task::yield_now().await; + tokio::time::advance(Duration::from_secs(1)).await; + + assert_eq!( + handle.await.expect("cloud requirements task"), + Some(ConfigRequirementsToml { + allowed_approval_policies: Some(vec![AskForApproval::Never]), + allowed_sandbox_modes: None, + mcp_servers: None, + rules: None, + enforce_residency: None, + }) + ); + assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 2); + } + + #[tokio::test] + async fn fetch_cloud_requirements_none_is_success_without_retry() { + let fetcher = Arc::new(SequenceFetcher::new(vec![ + Ok(None), + Err(FetchCloudRequirementsStatus::Request), + ])); + let service = CloudRequirementsService::new( + auth_manager_with_plan("enterprise"), + fetcher.clone(), + CLOUD_REQUIREMENTS_TIMEOUT, + ); + + assert!(service.fetch().await.is_none()); + assert_eq!(fetcher.request_count.load(Ordering::SeqCst), 1); + } + + #[tokio::test(start_paused = true)] + async fn fetch_cloud_requirements_stops_after_max_retries() { + let fetcher = Arc::new(SequenceFetcher::new(vec![ + Err( + FetchCloudRequirementsStatus::Request + ); + CLOUD_REQUIREMENTS_MAX_ATTEMPTS + ])); + let service = CloudRequirementsService::new( + auth_manager_with_plan("enterprise"), + fetcher.clone(), + CLOUD_REQUIREMENTS_TIMEOUT, + ); + + let handle = tokio::spawn(async move { service.fetch().await }); + tokio::task::yield_now().await; + tokio::time::advance(Duration::from_secs(5)).await; + tokio::task::yield_now().await; + + assert!(handle.await.expect("cloud requirements task").is_none()); + assert_eq!( + fetcher.request_count.load(Ordering::SeqCst), + CLOUD_REQUIREMENTS_MAX_ATTEMPTS + ); + } } diff --git a/codex-rs/core/src/util.rs b/codex-rs/core/src/util.rs index 1a538da558..59fecb0a9e 100644 --- a/codex-rs/core/src/util.rs +++ b/codex-rs/core/src/util.rs @@ -37,7 +37,7 @@ macro_rules! feedback_tags { }; } -pub(crate) fn backoff(attempt: u64) -> Duration { +pub fn backoff(attempt: u64) -> Duration { let exp = BACKOFF_FACTOR.powi(attempt.saturating_sub(1) as i32); let base = (INITIAL_DELAY_MS as f64 * exp) as u64; let jitter = rand::rng().random_range(0.9..1.1); From 73f32840c6efc86fead6b5af16031680d66f7241 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Wed, 4 Feb 2026 19:03:14 -0800 Subject: [PATCH 41/90] chore(core) personality migration tests (#10650) ## Summary Adds additional tests for personality edge cases ## Testing - [x] These are tests --- .../app-server/tests/suite/v2/turn_start.rs | 94 ++++++++++ .../core/tests/suite/personality_migration.rs | 176 ++++++++++++++++++ 2 files changed, 270 insertions(+) diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 2fc68e0beb..bbf94f1bd0 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -2,6 +2,7 @@ use anyhow::Result; use app_test_support::McpProcess; use app_test_support::create_apply_patch_sse_response; use app_test_support::create_exec_command_sse_response; +use app_test_support::create_fake_rollout; use app_test_support::create_final_assistant_message_sse_response; use app_test_support::create_mock_responses_server_sequence; use app_test_support::create_mock_responses_server_sequence_unchecked; @@ -34,8 +35,10 @@ use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStartedNotification; use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::UserInput as V2UserInput; +use codex_core::config::ConfigToml; use codex_core::features::FEATURES; use codex_core::features::Feature; +use codex_core::personality_migration::PERSONALITY_MIGRATION_FILENAME; use codex_core::protocol_config_types::ReasoningSummary; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; @@ -52,6 +55,7 @@ use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); const TEST_ORIGINATOR: &str = "codex_vscode"; +const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer."; #[tokio::test] async fn turn_start_sends_originator_header() -> Result<()> { @@ -595,6 +599,96 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { Ok(()) } +#[tokio::test] +async fn turn_start_uses_migrated_pragmatic_personality_without_override_v2() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = responses::start_mock_server().await; + let body = responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_assistant_message("msg-1", "Done"), + responses::ev_completed("resp-1"), + ]); + let response_mock = responses::mount_sse_once(&server, body).await; + + let codex_home = TempDir::new()?; + create_config_toml( + codex_home.path(), + &server.uri(), + "never", + &BTreeMap::from([(Feature::Personality, true)]), + )?; + create_fake_rollout( + codex_home.path(), + "2025-01-01T00-00-00", + "2025-01-01T00:00:00Z", + "history user message", + Some("mock_provider"), + None, + )?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let persisted_toml: ConfigToml = toml::from_str(&std::fs::read_to_string( + codex_home.path().join("config.toml"), + )?)?; + assert_eq!(persisted_toml.personality, Some(Personality::Pragmatic)); + assert!( + codex_home + .path() + .join(PERSONALITY_MIGRATION_FILENAME) + .exists(), + "expected personality migration marker to be written on startup" + ); + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("gpt-5.2-codex".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id, + input: vec![V2UserInput::Text { + text: "Hello".to_string(), + text_elements: Vec::new(), + }], + personality: None, + ..Default::default() + }) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + let _turn: TurnStartResponse = to_response::(turn_resp)?; + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let request = response_mock.single_request(); + let instructions_text = request.instructions_text(); + assert!( + instructions_text.contains(LOCAL_PRAGMATIC_TEMPLATE), + "expected startup-migrated pragmatic personality in model instructions, got: {instructions_text:?}" + ); + + Ok(()) +} + #[tokio::test] async fn turn_start_accepts_local_image_input() -> Result<()> { // Two Codex turns hit the mock model (session start + turn/start). diff --git a/codex-rs/core/tests/suite/personality_migration.rs b/codex-rs/core/tests/suite/personality_migration.rs index 492b4fdd22..2ca7aeeefd 100644 --- a/codex-rs/core/tests/suite/personality_migration.rs +++ b/codex-rs/core/tests/suite/personality_migration.rs @@ -42,6 +42,16 @@ async fn write_archived_session_with_user_event(codex_home: &Path) -> io::Result write_rollout_with_user_event(&dir, thread_id).await } +async fn write_session_with_meta_only(codex_home: &Path) -> io::Result<()> { + let thread_id = ThreadId::new(); + let dir = codex_home + .join(SESSIONS_SUBDIR) + .join("2025") + .join("01") + .join("01"); + write_rollout_with_meta_only(&dir, thread_id).await +} + async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::Result<()> { tokio::fs::create_dir_all(&dir).await?; let file_path = dir.join(format!("rollout-{TEST_TIMESTAMP}-{thread_id}.jsonl")); @@ -83,6 +93,40 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R Ok(()) } +async fn write_rollout_with_meta_only(dir: &Path, thread_id: ThreadId) -> io::Result<()> { + tokio::fs::create_dir_all(&dir).await?; + let file_path = dir.join(format!("rollout-{TEST_TIMESTAMP}-{thread_id}.jsonl")); + let mut file = tokio::fs::File::create(&file_path).await?; + + let session_meta = SessionMetaLine { + meta: SessionMeta { + id: thread_id, + forked_from_id: None, + timestamp: TEST_TIMESTAMP.to_string(), + cwd: std::path::PathBuf::from("."), + originator: "test_originator".to_string(), + cli_version: "test_version".to_string(), + source: SessionSource::Cli, + model_provider: None, + base_instructions: None, + dynamic_tools: None, + }, + git: None, + }; + let meta_line = RolloutLine { + timestamp: TEST_TIMESTAMP.to_string(), + item: RolloutItem::SessionMeta(session_meta), + }; + + let meta_json = serde_json::to_string(&meta_line)?; + file.write_all(format!("{meta_json}\n").as_bytes()).await?; + Ok(()) +} + +fn parse_config_toml(contents: &str) -> io::Result { + toml::from_str(contents).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) +} + #[tokio::test] async fn migration_marker_exists_no_sessions_no_change() -> io::Result<()> { let temp = TempDir::new()?; @@ -135,6 +179,138 @@ async fn no_marker_sessions_sets_personality() -> io::Result<()> { Ok(()) } +#[tokio::test] +async fn no_marker_sessions_preserves_existing_config_fields() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_user_event(temp.path()).await?; + tokio::fs::write(temp.path().join("config.toml"), "model = \"gpt-5-codex\"\n").await?; + let config_toml = read_config_toml(temp.path()).await?; + + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!(status, PersonalityMigrationStatus::Applied); + let persisted = read_config_toml(temp.path()).await?; + assert_eq!(persisted.model, Some("gpt-5-codex".to_string())); + assert_eq!(persisted.personality, Some(Personality::Pragmatic)); + Ok(()) +} + +#[tokio::test] +async fn no_marker_meta_only_rollout_is_treated_as_no_sessions() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_meta_only(temp.path()).await?; + + let status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + + assert_eq!(status, PersonalityMigrationStatus::SkippedNoSessions); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + true + ); + assert_eq!( + tokio::fs::try_exists(temp.path().join("config.toml")).await?, + false + ); + Ok(()) +} + +#[tokio::test] +async fn no_marker_explicit_global_personality_skips_migration() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_user_event(temp.path()).await?; + let config_toml = parse_config_toml("personality = \"friendly\"\n")?; + + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!( + status, + PersonalityMigrationStatus::SkippedExplicitPersonality + ); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + true + ); + assert_eq!( + tokio::fs::try_exists(temp.path().join("config.toml")).await?, + false + ); + Ok(()) +} + +#[tokio::test] +async fn no_marker_profile_personality_skips_migration() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_user_event(temp.path()).await?; + let config_toml = parse_config_toml( + r#" +profile = "work" + +[profiles.work] +personality = "friendly" +"#, + )?; + + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!( + status, + PersonalityMigrationStatus::SkippedExplicitPersonality + ); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + true + ); + assert_eq!( + tokio::fs::try_exists(temp.path().join("config.toml")).await?, + false + ); + Ok(()) +} + +#[tokio::test] +async fn marker_short_circuits_invalid_profile_resolution() -> io::Result<()> { + let temp = TempDir::new()?; + tokio::fs::write(temp.path().join(PERSONALITY_MIGRATION_FILENAME), "v1\n").await?; + let config_toml = parse_config_toml("profile = \"missing\"\n")?; + + let status = maybe_migrate_personality(temp.path(), &config_toml).await?; + + assert_eq!(status, PersonalityMigrationStatus::SkippedMarker); + Ok(()) +} + +#[tokio::test] +async fn invalid_selected_profile_returns_error_and_does_not_write_marker() -> io::Result<()> { + let temp = TempDir::new()?; + let config_toml = parse_config_toml("profile = \"missing\"\n")?; + + let err = maybe_migrate_personality(temp.path(), &config_toml) + .await + .expect_err("missing profile should fail"); + + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + assert_eq!( + tokio::fs::try_exists(temp.path().join(PERSONALITY_MIGRATION_FILENAME)).await?, + false + ); + Ok(()) +} + +#[tokio::test] +async fn applied_migration_is_idempotent_on_second_run() -> io::Result<()> { + let temp = TempDir::new()?; + write_session_with_user_event(temp.path()).await?; + + let first_status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + let second_status = maybe_migrate_personality(temp.path(), &ConfigToml::default()).await?; + + assert_eq!(first_status, PersonalityMigrationStatus::Applied); + assert_eq!(second_status, PersonalityMigrationStatus::SkippedMarker); + let persisted = read_config_toml(temp.path()).await?; + assert_eq!(persisted.personality, Some(Personality::Pragmatic)); + Ok(()) +} + #[tokio::test] async fn no_marker_archived_sessions_sets_personality() -> io::Result<()> { let temp = TempDir::new()?; From d876f3b94fa76c9d169d032112592233ab5568f8 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 4 Feb 2026 19:28:13 -0800 Subject: [PATCH 42/90] fix(tui): restore working shimmer after preamble output (#10701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When a turn streamed a preamble line before any tool activity, `ChatWidget` hid the status row while committing streamed lines and did not restore it until a later event (commonly `ExecCommandBegin`). During that idle gap, the UI looked finished even though the turn was still active. ## Mental model The bottom status row and transcript stream are separate progress affordances: - transcript stream shows committed output - status row (spinner/shimmer + header) shows liveness of an active turn While stream output is actively committing, hiding the status row is acceptable to avoid redundant visual noise. Once stream controllers go idle, an active turn must restore the status row immediately so liveness remains visible across preamble-to-tool gaps. ## Non-goals - No changes to streaming chunking policy or pacing. - No changes to final completion behavior (status still hides when task actually ends). - No refactor of status lifecycle ownership between `ChatWidget` and `BottomPane`. ## Tradeoffs - We keep the existing behavior of hiding the status row during active stream commits. - We add explicit restoration on the idle boundary when the task is still running. - This introduces one extra status update on idle transitions, which is small overhead but makes liveness semantics consistent. ## Architecture `run_commit_tick_with_scope` in `chatwidget.rs` now documents and enforces a two-phase contract: 1. For each committed streamed cell, hide status and append transcript output. 2. If controllers are present and all idle, restore status iff task is still running, preserving the current header. This keeps status ownership in `ChatWidget` while relying on `BottomPane` helpers: - `hide_status_indicator()` during active stream commits - `ensure_status_indicator()` + `set_status_header(current_status_header)` at stream-idle boundary Documentation pass additions: - Clarified the function-level contract and lifecycle intent in `run_commit_tick_with_scope`. - Added an explicit regression snapshot test comment describing the failing sequence. ## Observability Signal that the fix is present: - In the preamble-idle state, rendered output still includes `• Working (… esc to interrupt)`. - New snapshot: `codex_tui__chatwidget__tests__preamble_keeps_working_status.snap`. Debug path for future regressions: - Start at `run_commit_tick_with_scope` for hide/restore transitions. - Verify `bottom_pane.is_task_running()` at idle transition. - Confirm `current_status_header` continuity when status is recreated. - Use the new snapshot and targeted test sequence to reproduce deterministic preamble-idle behavior. ## Tests - Updated regression assertion: - `streaming_final_answer_keeps_task_running_state` now expects status widget to remain present while turn is running. - Renamed/updated behavioral regression: - `preamble_keeps_status_indicator_visible_until_exec_begin`. - Added snapshot regression coverage: - `preamble_keeps_working_status_snapshot`. - Snapshot file: `tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__preamble_keeps_working_status.snap`. Commands run: - `just fmt` - `cargo test -p codex-tui preamble_keeps_status_indicator_visible_until_exec_begin` - `cargo test -p codex-tui preamble_keeps_working_status_snapshot` ## Risks / Inconsistencies - Status visibility policy is still split across multiple event paths (`commit tick`, `turn complete`, `exec begin`), so future regressions can reintroduce ordering gaps. - Restoration depends on `is_task_running()` correctness; if task lifecycle flags drift, status behavior will drift too. - Snapshot proves rendered state, not animation cadence; cadence still relies on frame scheduling behavior elsewhere. --- MODULE.bazel.lock | 532 ++++++++++-------- codex-rs/tui/src/chatwidget.rs | 9 +- ..._tests__preamble_keeps_working_status.snap | 11 + codex-rs/tui/src/chatwidget/tests.rs | 27 +- 4 files changed, 339 insertions(+), 240 deletions(-) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__preamble_keeps_working_status.snap diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 63b7ed16ee..876bb2c57e 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -558,37 +558,41 @@ "actix-service_2.0.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"actix-utils\",\"req\":\"^3\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.17\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.17\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"}],\"features\":{}}", "actix-utils_3.0.1": "{\"dependencies\":[{\"name\":\"local-waker\",\"req\":\"^0.1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"}],\"features\":{}}", "actix-web_4.12.1": "{\"dependencies\":[{\"name\":\"actix-codec\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"actix-files\",\"req\":\"^0.6\"},{\"name\":\"actix-http\",\"req\":\"^3.11.2\"},{\"name\":\"actix-macros\",\"optional\":true,\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"http\"],\"name\":\"actix-router\",\"req\":\"^0.5.3\"},{\"default_features\":false,\"name\":\"actix-rt\",\"req\":\"^2.6\"},{\"name\":\"actix-server\",\"req\":\"^2.6\"},{\"name\":\"actix-service\",\"req\":\"^2\"},{\"features\":[\"openssl\",\"rustls-0_23\"],\"kind\":\"dev\",\"name\":\"actix-test\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"actix-tls\",\"optional\":true,\"req\":\"^3.4\"},{\"name\":\"actix-utils\",\"req\":\"^3\"},{\"default_features\":false,\"name\":\"actix-web-codegen\",\"optional\":true,\"req\":\"^4.3\"},{\"features\":[\"openssl\"],\"kind\":\"dev\",\"name\":\"awc\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"brotli\",\"req\":\"^8\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"bytestring\",\"req\":\"^1\"},{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"const-str\",\"req\":\"^0.5\"},{\"features\":[\"percent-encode\"],\"name\":\"cookie\",\"optional\":true,\"req\":\"^0.16\"},{\"kind\":\"dev\",\"name\":\"core_affinity\",\"req\":\"^0.8\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"features\":[\"as_ref\",\"deref\",\"deref_mut\",\"display\",\"error\",\"from\"],\"name\":\"derive_more\",\"req\":\"^2\"},{\"name\":\"encoding_rs\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0.13\"},{\"name\":\"foldhash\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.17\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3.17\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.17\"},{\"name\":\"impl-more\",\"req\":\"^0.1.4\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"name\":\"language-tags\",\"req\":\"^0.3\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"mime\",\"req\":\"^0.3\"},{\"name\":\"once_cell\",\"req\":\"^1.21\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.13\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.5.5\"},{\"name\":\"regex-lite\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"rustls-pemfile\",\"req\":\"^2\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7\"},{\"name\":\"smallvec\",\"req\":\"^1.6.1\"},{\"name\":\"socket2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"formatting\"],\"name\":\"time\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tls-openssl\",\"package\":\"openssl\",\"req\":\"^0.10.55\"},{\"kind\":\"dev\",\"name\":\"tls-rustls\",\"package\":\"rustls\",\"req\":\"^0.23\"},{\"features\":[\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.38.2\"},{\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tracing\",\"req\":\"^0.1.30\"},{\"name\":\"url\",\"req\":\"^2.5.4\"},{\"kind\":\"dev\",\"name\":\"zstd\",\"req\":\"^0.13\"}],\"features\":{\"__compress\":[],\"__tls\":[],\"compat\":[\"compat-routing-macros-force-pub\"],\"compat-routing-macros-force-pub\":[\"actix-web-codegen?/compat-routing-macros-force-pub\"],\"compress-brotli\":[\"actix-http/compress-brotli\",\"__compress\"],\"compress-gzip\":[\"actix-http/compress-gzip\",\"__compress\"],\"compress-zstd\":[\"actix-http/compress-zstd\",\"__compress\"],\"cookies\":[\"dep:cookie\"],\"default\":[\"macros\",\"compress-brotli\",\"compress-gzip\",\"compress-zstd\",\"cookies\",\"http2\",\"unicode\",\"compat\",\"ws\"],\"experimental-io-uring\":[\"actix-server/io-uring\"],\"http2\":[\"actix-http/http2\"],\"macros\":[\"dep:actix-macros\",\"dep:actix-web-codegen\"],\"openssl\":[\"__tls\",\"http2\",\"actix-http/openssl\",\"actix-tls/accept\",\"actix-tls/openssl\"],\"rustls\":[\"rustls-0_20\"],\"rustls-0_20\":[\"__tls\",\"http2\",\"actix-http/rustls-0_20\",\"actix-tls/accept\",\"actix-tls/rustls-0_20\"],\"rustls-0_21\":[\"__tls\",\"http2\",\"actix-http/rustls-0_21\",\"actix-tls/accept\",\"actix-tls/rustls-0_21\"],\"rustls-0_22\":[\"__tls\",\"http2\",\"actix-http/rustls-0_22\",\"actix-tls/accept\",\"actix-tls/rustls-0_22\"],\"rustls-0_23\":[\"__tls\",\"http2\",\"actix-http/rustls-0_23\",\"actix-tls/accept\",\"actix-tls/rustls-0_23\"],\"secure-cookies\":[\"cookies\",\"cookie/secure\"],\"unicode\":[\"dep:regex\",\"actix-router/unicode\"],\"ws\":[\"actix-http/ws\"]}}", - "addr2line_0.24.2": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"backtrace\",\"req\":\"^0.3.13\"},{\"features\":[\"wrap_help\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.3.21\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"cpp_demangle\",\"optional\":true,\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"findshlibs\",\"req\":\"^0.10\"},{\"default_features\":false,\"features\":[\"read\"],\"name\":\"gimli\",\"req\":\"^0.31.1\"},{\"kind\":\"dev\",\"name\":\"libtest-mimic\",\"req\":\"^0.7.2\"},{\"name\":\"memmap2\",\"optional\":true,\"req\":\"^0.9.4\"},{\"default_features\":false,\"features\":[\"read\",\"compression\"],\"name\":\"object\",\"optional\":true,\"req\":\"^0.36.0\"},{\"name\":\"rustc-demangle\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"typed-arena\",\"optional\":true,\"req\":\"^2\"}],\"features\":{\"all\":[\"bin\"],\"bin\":[\"loader\",\"rustc-demangle\",\"cpp_demangle\",\"fallible-iterator\",\"smallvec\",\"dep:clap\"],\"cargo-all\":[],\"default\":[\"rustc-demangle\",\"cpp_demangle\",\"loader\",\"fallible-iterator\",\"smallvec\"],\"loader\":[\"std\",\"dep:object\",\"dep:memmap2\",\"dep:typed-arena\"],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"compiler_builtins\",\"gimli/rustc-dep-of-std\"],\"std\":[\"gimli/std\"]}}", + "addr2line_0.25.1": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"backtrace\",\"req\":\"^0.3.13\"},{\"features\":[\"wrap_help\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.3.21\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"cpp_demangle\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"findshlibs\",\"req\":\"^0.10\"},{\"default_features\":false,\"features\":[\"read\"],\"name\":\"gimli\",\"req\":\"^0.32.0\"},{\"kind\":\"dev\",\"name\":\"libtest-mimic\",\"req\":\"^0.8.1\"},{\"name\":\"memmap2\",\"optional\":true,\"req\":\"^0.9.4\"},{\"default_features\":false,\"features\":[\"read\",\"compression\"],\"name\":\"object\",\"optional\":true,\"req\":\"^0.37.0\"},{\"name\":\"rustc-demangle\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"typed-arena\",\"optional\":true,\"req\":\"^2\"}],\"features\":{\"all\":[\"bin\",\"wasm\"],\"bin\":[\"loader\",\"rustc-demangle\",\"cpp_demangle\",\"fallible-iterator\",\"smallvec\",\"dep:clap\"],\"cargo-all\":[],\"default\":[\"rustc-demangle\",\"cpp_demangle\",\"loader\",\"fallible-iterator\",\"smallvec\"],\"loader\":[\"std\",\"dep:object\",\"dep:memmap2\",\"dep:typed-arena\"],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"gimli/rustc-dep-of-std\"],\"std\":[\"gimli/std\"],\"wasm\":[\"object/wasm\"]}}", "adler2_2.0.1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"std\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[]}}", + "aead_0.5.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"blobby\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"crypto-common\",\"req\":\"^0.1.4\"},{\"default_features\":false,\"name\":\"generic-array\",\"req\":\"^0.14\"},{\"default_features\":false,\"name\":\"heapless\",\"optional\":true,\"req\":\"^0.7\"}],\"features\":{\"alloc\":[],\"default\":[\"rand_core\"],\"dev\":[\"blobby\"],\"getrandom\":[\"crypto-common/getrandom\",\"rand_core\"],\"rand_core\":[\"crypto-common/rand_core\"],\"std\":[\"alloc\",\"crypto-common/std\"],\"stream\":[]}}", "aes_0.8.4": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"aarch64\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.5.6\",\"target\":\"cfg(all(aes_armv8, target_arch = \\\"aarch64\\\"))\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.6.0\",\"target\":\"cfg(not(all(aes_armv8, target_arch = \\\"aarch64\\\")))\"}],\"features\":{\"hazmat\":[]}}", + "age-core_0.11.0": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.21\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"chacha20poly1305\",\"req\":\"^0.10\"},{\"name\":\"cookie-factory\",\"req\":\"^0.3.1\"},{\"name\":\"hkdf\",\"req\":\"^0.12\"},{\"name\":\"io_tee\",\"req\":\"^0.1.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"nom\",\"req\":\"^7\"},{\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"secrecy\",\"req\":\"^0.10\"},{\"name\":\"sha2\",\"req\":\"^0.10\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.2.0\"}],\"features\":{\"plugin\":[\"tempfile\"],\"unstable\":[]}}", + "age_0.11.2": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"aes-gcm\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"age-core\",\"req\":\"^0.11.0\"},{\"name\":\"base64\",\"req\":\"^0.21\"},{\"name\":\"bcrypt-pbkdf\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"bech32\",\"req\":\"^0.9\"},{\"name\":\"cbc\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"chacha20poly1305\",\"req\":\"^0.10\"},{\"features\":[\"alloc\"],\"name\":\"cipher\",\"optional\":true,\"req\":\"^0.4.3\"},{\"default_features\":false,\"name\":\"console\",\"optional\":true,\"req\":\"^0.15\"},{\"name\":\"cookie-factory\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"criterion-cycles-per-byte\",\"req\":\"^0.6\",\"target\":\"cfg(any(target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"ctr\",\"optional\":true,\"req\":\"^0.9\"},{\"name\":\"curve25519-dalek\",\"optional\":true,\"req\":\"^4\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"futures-test\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"hmac\",\"req\":\"^0.12\"},{\"features\":[\"fluent-system\"],\"name\":\"i18n-embed\",\"req\":\"^0.15\"},{\"features\":[\"fluent-system\",\"desktop-requester\"],\"kind\":\"dev\",\"name\":\"i18n-embed\",\"req\":\"^0.15\"},{\"name\":\"i18n-embed-fl\",\"req\":\"^0.9\"},{\"name\":\"is-terminal\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"nom\",\"req\":\"^7\"},{\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"pin-project\",\"req\":\"^1\"},{\"name\":\"pinentry\",\"optional\":true,\"req\":\"^0.6\"},{\"features\":[\"criterion\",\"flamegraph\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.13\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"rpassword\",\"optional\":true,\"req\":\"^7\"},{\"default_features\":false,\"name\":\"rsa\",\"optional\":true,\"req\":\"^0.9\"},{\"name\":\"rust-embed\",\"req\":\"^8\"},{\"default_features\":false,\"name\":\"scrypt\",\"req\":\"^0.11\"},{\"name\":\"sha2\",\"req\":\"^0.10\"},{\"name\":\"subtle\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"test-case\",\"req\":\"^3\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"Window\",\"Performance\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"which\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(any(unix, windows))\"},{\"name\":\"wsl\",\"optional\":true,\"req\":\"^0.1\",\"target\":\"cfg(any(unix, windows))\"},{\"features\":[\"static_secrets\"],\"name\":\"x25519-dalek\",\"req\":\"^2\"},{\"name\":\"zeroize\",\"req\":\"^1\"}],\"features\":{\"armor\":[],\"async\":[\"futures\",\"memchr\"],\"cli-common\":[\"console\",\"is-terminal\",\"pinentry\",\"rpassword\"],\"default\":[],\"plugin\":[\"age-core/plugin\",\"which\",\"wsl\"],\"ssh\":[\"aes\",\"aes-gcm\",\"bcrypt-pbkdf\",\"cbc\",\"cipher\",\"ctr\",\"curve25519-dalek\",\"num-traits\",\"rsa\"],\"unstable\":[\"age-core/unstable\"]}}", "ahash_0.8.12": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"const-random\",\"optional\":true,\"req\":\"^0.1.17\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.2\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"hashbrown\",\"req\":\"^0.14.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"no-panic\",\"req\":\"^0.1.10\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"once_cell\",\"req\":\"^1.18.0\",\"target\":\"cfg(not(all(target_arch = \\\"arm\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"pcg-mwc\",\"req\":\"^0.2.1\"},{\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"seahash\",\"req\":\"^4.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.59\"},{\"kind\":\"dev\",\"name\":\"smallvec\",\"req\":\"^1.13.1\"},{\"kind\":\"build\",\"name\":\"version_check\",\"req\":\"^0.9.4\"},{\"default_features\":false,\"features\":[\"simd\"],\"name\":\"zerocopy\",\"req\":\"^0.8.24\"}],\"features\":{\"atomic-polyfill\":[\"dep:portable-atomic\",\"once_cell/critical-section\"],\"compile-time-rng\":[\"const-random\"],\"default\":[\"std\",\"runtime-rng\"],\"nightly-arm-aes\":[],\"no-rng\":[],\"runtime-rng\":[\"getrandom\"],\"std\":[]}}", - "aho-corasick_1.1.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"std\",\"perf-literal\"],\"logging\":[\"dep:log\"],\"perf-literal\":[\"dep:memchr\"],\"std\":[\"memchr?/std\"]}}", + "aho-corasick_1.1.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"std\",\"perf-literal\"],\"logging\":[\"dep:log\"],\"perf-literal\":[\"dep:memchr\"],\"std\":[\"memchr?/std\"]}}", "allocative_0.3.4": "{\"dependencies\":[{\"name\":\"allocative_derive\",\"req\":\"=0.3.3\"},{\"name\":\"anyhow\",\"optional\":true,\"req\":\"^1.0.65\"},{\"name\":\"bumpalo\",\"optional\":true,\"req\":\"^3.11.1\"},{\"name\":\"compact_str\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"ctor\",\"req\":\"^0.1.26\"},{\"name\":\"dashmap\",\"optional\":true,\"req\":\"^5.5.3\"},{\"name\":\"either\",\"optional\":true,\"req\":\"^1.8\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.24\"},{\"features\":[\"raw\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.14.3\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.2.6\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"inferno\",\"req\":\"^0.11.11\"},{\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.15.0\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.11.2\"},{\"name\":\"prost-types\",\"optional\":true,\"req\":\"^0.11.2\"},{\"name\":\"relative-path\",\"optional\":true,\"req\":\"^1.7.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.48\"},{\"name\":\"slab\",\"optional\":true,\"req\":\"^0.4.7\"},{\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.10.0\"},{\"name\":\"sorted_vector_map\",\"optional\":true,\"req\":\"^0.2\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.5\"},{\"name\":\"triomphe\",\"optional\":true,\"req\":\"^0.1.8\"}],\"features\":{}}", "allocative_derive_0.3.3": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0.3\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "allocator-api2_0.2.21": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"fresh-rust\":[],\"nightly\":[],\"std\":[\"alloc\"]}}", "android_system_properties_0.1.5": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.126\"}],\"features\":{}}", "annotate-snippets_0.9.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"difference\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.5\"},{\"name\":\"unicode-width\",\"req\":\"^0.1\"},{\"name\":\"yansi-term\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"yansi-term\",\"req\":\"^0.1\"}],\"features\":{\"color\":[\"yansi-term\"],\"default\":[]}}", "ansi-to-tui_7.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"nom\",\"req\":\"^7.1\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"name\":\"simdutf8\",\"optional\":true,\"req\":\"^0.1\"},{\"features\":[\"const_generics\"],\"name\":\"smallvec\",\"req\":\"^1.10.0\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tui\",\"package\":\"ratatui\",\"req\":\"^0.29\"}],\"features\":{\"default\":[\"zero-copy\",\"simd\"],\"simd\":[\"dep:simdutf8\"],\"zero-copy\":[]}}", - "anstream_0.6.19": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"name\":\"anstyle-parse\",\"req\":\"^0.2.0\"},{\"name\":\"anstyle-query\",\"optional\":true,\"req\":\"^1.0.0\"},{\"name\":\"anstyle-wincon\",\"optional\":true,\"req\":\"^3.0.5\",\"target\":\"cfg(windows)\"},{\"name\":\"colorchoice\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"divan\",\"req\":\"^0.1.11\"},{\"name\":\"is_terminal_polyfill\",\"req\":\"^1.48\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"owo-colors\",\"req\":\"^4.0.0\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.4.0\"},{\"kind\":\"dev\",\"name\":\"strip-ansi-escapes\",\"req\":\"^0.2.0\"},{\"name\":\"utf8parse\",\"req\":\"^0.2.1\"}],\"features\":{\"auto\":[\"dep:anstyle-query\"],\"default\":[\"auto\",\"wincon\"],\"test\":[],\"wincon\":[\"dep:anstyle-wincon\"]}}", + "anstream_0.6.21": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"name\":\"anstyle-parse\",\"req\":\"^0.2.0\"},{\"name\":\"anstyle-query\",\"optional\":true,\"req\":\"^1.0.0\"},{\"name\":\"anstyle-wincon\",\"optional\":true,\"req\":\"^3.0.5\",\"target\":\"cfg(windows)\"},{\"name\":\"colorchoice\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"divan\",\"req\":\"^0.1.16\"},{\"name\":\"is_terminal_polyfill\",\"req\":\"^1.48\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"owo-colors\",\"req\":\"^4.0.0\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"strip-ansi-escapes\",\"req\":\"^0.2.1\"},{\"name\":\"utf8parse\",\"req\":\"^0.2.2\"}],\"features\":{\"auto\":[\"dep:anstyle-query\"],\"default\":[\"auto\",\"wincon\"],\"test\":[],\"wincon\":[\"dep:anstyle-wincon\"]}}", "anstyle-parse_0.2.7": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec\",\"optional\":true,\"req\":\"^0.7.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"codegenrs\",\"req\":\"^3.0.1\"},{\"kind\":\"dev\",\"name\":\"divan\",\"req\":\"^0.1.14\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.4.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.5\"},{\"name\":\"utf8parse\",\"optional\":true,\"req\":\"^0.2.1\"},{\"kind\":\"dev\",\"name\":\"vte_generate_state_changes\",\"req\":\"^0.1.1\"}],\"features\":{\"core\":[\"dep:arrayvec\"],\"default\":[\"utf8\"],\"utf8\":[\"dep:utf8parse\"]}}", - "anstyle-query_1.1.3": "{\"dependencies\":[{\"features\":[\"Win32_System_Console\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\"^0.59.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "anstyle-wincon_3.0.9": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.0\"},{\"name\":\"once_cell_polyfill\",\"req\":\"^1.56.0\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_System_Console\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\"^0.59.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "anstyle_1.0.11": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "anstyle-query_1.1.5": "{\"dependencies\":[{\"features\":[\"Win32_System_Console\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.60.2, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "anstyle-wincon_3.0.11": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"name\":\"once_cell_polyfill\",\"req\":\"^1.56.1\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_System_Console\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.60.2, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "anstyle_1.0.13": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.5\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "anyhow_1.0.100": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.51\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.6\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "arbitrary_1.4.2": "{\"dependencies\":[{\"name\":\"derive_arbitrary\",\"optional\":true,\"req\":\"~1.4.0\"},{\"kind\":\"dev\",\"name\":\"exhaustigen\",\"req\":\"^0.1.0\"}],\"features\":{\"derive\":[\"derive_arbitrary\"]}}", "arboard_3.6.1": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"clipboard-win\",\"req\":\"^5.3.1\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10.2\"},{\"default_features\":false,\"features\":[\"png\"],\"name\":\"image\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"default_features\":false,\"features\":[\"tiff\"],\"name\":\"image\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"png\",\"bmp\"],\"name\":\"image\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(windows)\"},{\"name\":\"log\",\"req\":\"^0.4\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"name\":\"log\",\"req\":\"^0.4\",\"target\":\"cfg(windows)\"},{\"name\":\"objc2\",\"req\":\"^0.6.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"objc2-core-graphics\",\"NSPasteboard\",\"NSPasteboardItem\",\"NSImage\"],\"name\":\"objc2-app-kit\",\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"CFCGTypes\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"CGImage\",\"CGColorSpace\",\"CGDataProvider\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"std\",\"NSArray\",\"NSString\",\"NSEnumerator\",\"NSGeometry\",\"NSValue\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3.1\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_DataExchange\",\"Win32_System_Memory\",\"Win32_System_Ole\",\"Win32_UI_Shell\"],\"name\":\"windows-sys\",\"req\":\">=0.52.0, <0.61.0\",\"target\":\"cfg(windows)\"},{\"name\":\"wl-clipboard-rs\",\"optional\":true,\"req\":\"^0.9.0\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"},{\"name\":\"x11rb\",\"req\":\"^0.13\",\"target\":\"cfg(all(unix, not(any(target_os=\\\"macos\\\", target_os=\\\"android\\\", target_os=\\\"emscripten\\\"))))\"}],\"features\":{\"core-graphics\":[\"dep:objc2-core-graphics\"],\"default\":[\"image-data\"],\"image\":[\"dep:image\"],\"image-data\":[\"dep:objc2-core-graphics\",\"dep:objc2-core-foundation\",\"image\",\"windows-sys\",\"core-graphics\"],\"wayland-data-control\":[\"wl-clipboard-rs\"],\"windows-sys\":[\"windows-sys/Win32_Graphics_Gdi\"],\"wl-clipboard-rs\":[\"dep:wl-clipboard-rs\"]}}", "arc-swap_1.8.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adaptive-barrier\",\"req\":\"~1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"~0.7\"},{\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"~0.8\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"~1\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"~1\"},{\"kind\":\"dev\",\"name\":\"parking_lot\",\"req\":\"~0.12\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"rustversion\",\"req\":\"^1\"},{\"features\":[\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.130\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.177\"}],\"features\":{\"experimental-strategies\":[],\"experimental-thread-local\":[],\"internal-test-strategies\":[],\"weak\":[]}}", "arrayvec_0.7.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.4\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"matches\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "ascii-canvas_3.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"diff\",\"req\":\"^0.1\"},{\"name\":\"term\",\"req\":\"^0.7\"}],\"features\":{}}", "ascii_1.1.0": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"name\":\"serde_test\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "assert-json-diff_2.0.2": "{\"dependencies\":[{\"name\":\"serde\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.8\"}],\"features\":{}}", - "assert_cmd_2.0.17": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.7\"},{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"name\":\"bstr\",\"req\":\"^1.0.1\"},{\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"escargot\",\"req\":\"^0.5\"},{\"name\":\"libc\",\"req\":\"^0.2.137\",\"target\":\"cfg(any())\"},{\"default_features\":false,\"features\":[\"diff\"],\"name\":\"predicates\",\"req\":\"^3.0.1\"},{\"name\":\"predicates-core\",\"req\":\"^1.0.6\"},{\"name\":\"predicates-tree\",\"req\":\"^1.0.1\"},{\"name\":\"wait-timeout\",\"req\":\"^0.2.0\"}],\"features\":{\"color\":[\"dep:anstream\",\"predicates/color\"],\"color-auto\":[\"color\"]}}", + "assert_cmd_2.1.2": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.7\"},{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"name\":\"bstr\",\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"escargot\",\"req\":\"^0.5\"},{\"name\":\"libc\",\"req\":\"^0.2.137\",\"target\":\"cfg(any())\"},{\"default_features\":false,\"features\":[\"diff\"],\"name\":\"predicates\",\"req\":\"^3.0.1\"},{\"name\":\"predicates-core\",\"req\":\"^1.0.6\"},{\"name\":\"predicates-tree\",\"req\":\"^1.0.1\"},{\"name\":\"wait-timeout\",\"req\":\"^0.2.0\"}],\"features\":{\"color\":[\"dep:anstream\",\"predicates/color\"],\"color-auto\":[\"color\"]}}", "assert_matches_1.5.0": "{\"dependencies\":[],\"features\":{}}", "async-broadcast_0.7.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.5\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.2.0\"},{\"name\":\"event-listener\",\"req\":\"^5.0.0\"},{\"name\":\"event-listener-strategy\",\"req\":\"^0.5.0\"},{\"name\":\"futures-core\",\"req\":\"^0.3.21\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^1.11.3\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.21\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.13\"}],\"features\":{}}", "async-channel_2.5.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"concurrent-queue\",\"req\":\"^2.5\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3\"},{\"default_features\":false,\"name\":\"event-listener-strategy\",\"req\":\"^0.5.4\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.5\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"portable-atomic-util\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.37\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"portable-atomic\":[\"concurrent-queue/portable-atomic\",\"event-listener-strategy/portable-atomic\",\"dep:portable-atomic-util\",\"dep:portable-atomic\"],\"std\":[\"concurrent-queue/std\",\"event-listener-strategy/std\"]}}", "async-executor_1.13.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-channel\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"async-io\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"async-lock\",\"req\":\"^3.0.0\"},{\"name\":\"async-task\",\"req\":\"^4.4.0\"},{\"name\":\"concurrent-queue\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.1.0\"},{\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-lite\",\"req\":\"^2.0.0\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.16.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"name\":\"slab\",\"req\":\"^0.4.7\"}],\"features\":{\"static\":[]}}", "async-fs_2.2.0": "{\"dependencies\":[{\"name\":\"async-lock\",\"req\":\"^3.0.0\"},{\"name\":\"blocking\",\"req\":\"^1.3.0\"},{\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.78\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Storage_FileSystem\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "async-io_2.6.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-channel\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"async-net\",\"req\":\"^2.0.0\"},{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"blocking\",\"req\":\"^1\"},{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"concurrent-queue\",\"req\":\"^2.2.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-io\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"inotify\",\"req\":\"^0.11.0\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"parking\",\"req\":\"^2.0.0\"},{\"name\":\"polling\",\"req\":\"^3.4.0\"},{\"default_features\":false,\"features\":[\"fs\",\"net\",\"std\"],\"name\":\"rustix\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"signal-hook\",\"req\":\"^0.3\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"timerfd\",\"req\":\"^1\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.37\"},{\"kind\":\"dev\",\"name\":\"uds_windows\",\"req\":\"^1\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "async-lock_3.4.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"event-listener\",\"req\":\"^5.0.0\"},{\"default_features\":false,\"name\":\"event-listener-strategy\",\"req\":\"^0.5.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"flume\",\"req\":\"^0.11.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"loom\":[\"event-listener/loom\",\"dep:loom\"],\"std\":[\"event-listener/std\",\"event-listener-strategy/std\"]}}", + "async-lock_3.4.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"event-listener\",\"req\":\"^5.0.0\"},{\"default_features\":false,\"name\":\"event-listener-strategy\",\"req\":\"^0.5.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"flume\",\"req\":\"^0.12.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"loom\":[\"event-listener/loom\",\"dep:loom\"],\"std\":[\"event-listener/std\",\"event-listener-strategy/std\"]}}", "async-process_2.5.0": "{\"dependencies\":[{\"name\":\"async-channel\",\"req\":\"^2.0.0\",\"target\":\"cfg(any(windows, target_os = \\\"linux\\\"))\"},{\"kind\":\"dev\",\"name\":\"async-executor\",\"req\":\"^1.5.1\"},{\"name\":\"async-io\",\"req\":\"^2.3.0\"},{\"name\":\"async-lock\",\"req\":\"^3.0.0\",\"target\":\"cfg(unix)\"},{\"name\":\"async-signal\",\"req\":\"^0.2.3\",\"target\":\"cfg(unix)\"},{\"name\":\"async-task\",\"req\":\"^4.7.0\",\"target\":\"cfg(any(windows, target_os = \\\"linux\\\"))\"},{\"name\":\"blocking\",\"req\":\"^1.0.0\",\"target\":\"cfg(windows)\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"event-listener\",\"req\":\"^5.1.0\"},{\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"features\":[\"std\",\"fs\",\"process\"],\"name\":\"rustix\",\"req\":\"^1.0\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.40\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Threading\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "async-recursion_1.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures-executor\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"macrotest\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"full\",\"visit-mut\",\"parsing\",\"printing\",\"proc-macro\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"}],\"features\":{}}", "async-signal_0.2.13": "{\"dependencies\":[{\"name\":\"async-io\",\"req\":\"^2.0.0\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"async-io\",\"req\":\"^2.0.0\"},{\"name\":\"async-lock\",\"req\":\"^3.3.0\",\"target\":\"cfg(windows)\"},{\"name\":\"atomic-waker\",\"req\":\"^1.1.1\",\"target\":\"cfg(windows)\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.1\"},{\"name\":\"futures-core\",\"req\":\"^0.3.26\"},{\"name\":\"futures-io\",\"req\":\"^0.3.26\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.3.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.139\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[\"process\",\"std\"],\"name\":\"rustix\",\"req\":\"^1.0.7\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"signal-hook\",\"req\":\"^0.3.14\"},{\"name\":\"signal-hook-registry\",\"req\":\"^1.4.0\",\"target\":\"cfg(unix)\"},{\"name\":\"slab\",\"req\":\"^0.4.8\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Console\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", @@ -602,9 +606,12 @@ "autocfg_1.5.0": "{\"dependencies\":[],\"features\":{}}", "axum-core_0.5.6": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1.0.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.0.0\"},{\"name\":\"mime\",\"req\":\"^0.3.16\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"},{\"name\":\"sync_wrapper\",\"req\":\"^1.0.0\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.25.0\"},{\"features\":[\"limit\"],\"name\":\"tower-http\",\"optional\":true,\"req\":\"^0.6.0\"},{\"features\":[\"limit\"],\"kind\":\"dev\",\"name\":\"tower-http\",\"req\":\"^0.6.0\"},{\"name\":\"tower-layer\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.37\"}],\"features\":{\"__private_docs\":[\"dep:tower-http\"],\"tracing\":[\"dep:tracing\"]}}", "axum_0.8.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"axum-core\",\"req\":\"^0.5.5\"},{\"name\":\"axum-macros\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22.1\"},{\"name\":\"bytes\",\"req\":\"^1.0\"},{\"name\":\"form_urlencoded\",\"optional\":true,\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1.0.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"optional\":true,\"req\":\"^1.1.0\"},{\"features\":[\"client\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1.0\"},{\"features\":[\"tokio\",\"server\",\"service\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1.3\"},{\"name\":\"itoa\",\"req\":\"^1.0.5\"},{\"name\":\"matchit\",\"req\":\"=0.8.4\"},{\"name\":\"memchr\",\"req\":\"^2.4.1\"},{\"name\":\"mime\",\"req\":\"^0.3.16\"},{\"name\":\"multer\",\"optional\":true,\"req\":\"^3.0.0\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"json\",\"stream\",\"multipart\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"json\",\"stream\",\"multipart\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.211\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"name\":\"serde_core\",\"req\":\"^1.0.221\"},{\"features\":[\"raw_value\"],\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"raw_value\"],\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"serde_path_to_error\",\"optional\":true,\"req\":\"^0.1.8\"},{\"name\":\"serde_urlencoded\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"sync_wrapper\",\"req\":\"^1.0.0\"},{\"features\":[\"serde-human-readable\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3\"},{\"features\":[\"time\"],\"name\":\"tokio\",\"optional\":true,\"package\":\"tokio\",\"req\":\"^1.44\"},{\"features\":[\"macros\",\"rt\",\"rt-multi-thread\",\"net\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"package\":\"tokio\",\"req\":\"^1.44.2\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"name\":\"tokio-tungstenite\",\"optional\":true,\"req\":\"^0.28.0\"},{\"kind\":\"dev\",\"name\":\"tokio-tungstenite\",\"req\":\"^0.28.0\"},{\"default_features\":false,\"features\":[\"util\"],\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"features\":[\"util\",\"timeout\",\"limit\",\"load-shed\",\"steer\",\"filter\"],\"kind\":\"dev\",\"name\":\"tower\",\"package\":\"tower\",\"req\":\"^0.5.2\"},{\"features\":[\"add-extension\",\"auth\",\"catch-panic\",\"compression-br\",\"compression-deflate\",\"compression-gzip\",\"cors\",\"decompression-br\",\"decompression-deflate\",\"decompression-gzip\",\"follow-redirect\",\"fs\",\"limit\",\"map-request-body\",\"map-response-body\",\"metrics\",\"normalize-path\",\"propagate-header\",\"redirect\",\"request-id\",\"sensitive-headers\",\"set-header\",\"set-status\",\"timeout\",\"trace\",\"util\",\"validate-request\"],\"name\":\"tower-http\",\"optional\":true,\"req\":\"^0.6.0\"},{\"features\":[\"add-extension\",\"auth\",\"catch-panic\",\"compression-br\",\"compression-deflate\",\"compression-gzip\",\"cors\",\"decompression-br\",\"decompression-deflate\",\"decompression-gzip\",\"follow-redirect\",\"fs\",\"limit\",\"map-request-body\",\"map-response-body\",\"metrics\",\"normalize-path\",\"propagate-header\",\"redirect\",\"request-id\",\"sensitive-headers\",\"set-header\",\"set-status\",\"timeout\",\"trace\",\"util\",\"validate-request\"],\"kind\":\"dev\",\"name\":\"tower-http\",\"req\":\"^0.6.0\"},{\"name\":\"tower-layer\",\"req\":\"^0.3.2\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"features\":[\"serde\",\"v4\"],\"kind\":\"dev\",\"name\":\"uuid\",\"req\":\"^1.0\"}],\"features\":{\"__private\":[\"tokio\",\"http1\",\"dep:reqwest\"],\"__private_docs\":[\"axum-core/__private_docs\",\"tower/full\",\"dep:serde\",\"dep:tower-http\"],\"default\":[\"form\",\"http1\",\"json\",\"matched-path\",\"original-uri\",\"query\",\"tokio\",\"tower-log\",\"tracing\"],\"form\":[\"dep:form_urlencoded\",\"dep:serde_urlencoded\",\"dep:serde_path_to_error\"],\"http1\":[\"dep:hyper\",\"hyper?/http1\",\"hyper-util?/http1\"],\"http2\":[\"dep:hyper\",\"hyper?/http2\",\"hyper-util?/http2\"],\"json\":[\"dep:serde_json\",\"dep:serde_path_to_error\"],\"macros\":[\"dep:axum-macros\"],\"matched-path\":[],\"multipart\":[\"dep:multer\"],\"original-uri\":[],\"query\":[\"dep:form_urlencoded\",\"dep:serde_urlencoded\",\"dep:serde_path_to_error\"],\"tokio\":[\"dep:hyper-util\",\"dep:tokio\",\"tokio/net\",\"tokio/rt\",\"tower/make\",\"tokio/macros\"],\"tower-log\":[\"tower/log\"],\"tracing\":[\"dep:tracing\",\"axum-core/tracing\"],\"ws\":[\"dep:hyper\",\"tokio\",\"dep:tokio-tungstenite\",\"dep:sha1\",\"dep:base64\"]}}", - "backtrace_0.3.75": "{\"dependencies\":[{\"default_features\":false,\"name\":\"addr2line\",\"req\":\"^0.24.0\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"cpp_demangle\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.156\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libloading\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"miniz_oxide\",\"req\":\"^0.8\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"default_features\":false,\"features\":[\"read_core\",\"elf\",\"macho\",\"pe\",\"xcoff\",\"unaligned\",\"archive\"],\"name\":\"object\",\"req\":\"^0.36.0\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"name\":\"rustc-demangle\",\"req\":\"^0.1.24\"},{\"default_features\":false,\"name\":\"ruzstd\",\"optional\":true,\"req\":\"^0.7.3\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"windows-targets\",\"req\":\"^0.52.6\",\"target\":\"cfg(any(windows, target_os = \\\"cygwin\\\"))\"}],\"features\":{\"coresymbolication\":[],\"dbghelp\":[],\"default\":[\"std\"],\"dl_iterate_phdr\":[],\"dladdr\":[],\"kernel32\":[],\"libunwind\":[],\"ruzstd\":[\"dep:ruzstd\"],\"serialize-serde\":[\"serde\"],\"std\":[],\"unix-backtrace\":[]}}", + "backtrace_0.3.76": "{\"dependencies\":[{\"default_features\":false,\"name\":\"addr2line\",\"req\":\"^0.25.0\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"cpp_demangle\",\"optional\":true,\"req\":\"^0.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.156\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libloading\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"miniz_oxide\",\"req\":\"^0.8\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"default_features\":false,\"features\":[\"read_core\",\"elf\",\"macho\",\"pe\",\"xcoff\",\"unaligned\",\"archive\"],\"name\":\"object\",\"req\":\"^0.37.0\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"name\":\"rustc-demangle\",\"req\":\"^0.1.24\"},{\"default_features\":false,\"name\":\"ruzstd\",\"optional\":true,\"req\":\"^0.8.1\",\"target\":\"cfg(not(all(windows, target_env = \\\"msvc\\\", not(target_vendor = \\\"uwp\\\"))))\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(any(windows, target_os = \\\"cygwin\\\"))\"}],\"features\":{\"coresymbolication\":[],\"dbghelp\":[],\"default\":[\"std\"],\"dl_iterate_phdr\":[],\"dladdr\":[],\"kernel32\":[],\"libunwind\":[],\"ruzstd\":[\"dep:ruzstd\"],\"serialize-serde\":[\"serde\"],\"std\":[],\"unix-backtrace\":[]}}", + "base64_0.21.7": "{\"dependencies\":[{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^3.2.25\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.13.0\"},{\"kind\":\"dev\",\"name\":\"rstest_reuse\",\"req\":\"^0.6.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.25\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "base64_0.22.1": "{\"dependencies\":[{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^3.2.25\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.13.0\"},{\"kind\":\"dev\",\"name\":\"rstest_reuse\",\"req\":\"^0.6.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.25\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", - "base64ct_1.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\"]}}", + "base64ct_1.8.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\"]}}", + "basic-toml_0.1.10": "{\"dependencies\":[{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"semver\",\"req\":\"^1.0.17\"},{\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.194\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.99\"}],\"features\":{}}", + "bech32_0.9.1": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[],\"strict\":[]}}", "beef_0.5.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.105\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.105\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"const_fn\":[],\"default\":[],\"impl_serde\":[\"serde\"]}}", "bindgen_0.72.1": "{\"dependencies\":[{\"name\":\"annotate-snippets\",\"optional\":true,\"req\":\"^0.11.4\"},{\"name\":\"bitflags\",\"req\":\"^2.2.1\"},{\"name\":\"cexpr\",\"req\":\"^0.6\"},{\"features\":[\"clang_11_0\"],\"name\":\"clang-sys\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4\"},{\"name\":\"clap_complete\",\"optional\":true,\"req\":\"^4\"},{\"default_features\":false,\"name\":\"itertools\",\"req\":\">=0.10, <0.14\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"verbatim\"],\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2.7\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-perl\"],\"name\":\"regex\",\"req\":\"^1.5.3\"},{\"name\":\"rustc-hash\",\"req\":\"^2.1.0\"},{\"name\":\"shlex\",\"req\":\"^1\"},{\"features\":[\"full\",\"extra-traits\",\"visit-mut\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{\"__cli\":[\"dep:clap\",\"dep:clap_complete\"],\"__testing_only_extra_assertions\":[],\"__testing_only_libclang_16\":[],\"__testing_only_libclang_9\":[],\"default\":[\"logging\",\"prettyplease\",\"runtime\"],\"experimental\":[\"dep:annotate-snippets\"],\"logging\":[\"dep:log\"],\"runtime\":[\"clang-sys/runtime\"],\"static\":[\"clang-sys/static\"]}}", "bit-set_0.5.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bit-vec\",\"req\":\"^0.6.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"bit-vec/std\"]}}", @@ -613,34 +620,39 @@ "bitflags_2.10.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"arbitrary\",\"req\":\"^1.0\"},{\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.12\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.12.2\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.228\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde_lib\",\"package\":\"serde\",\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.18\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"zerocopy\",\"req\":\"^0.8\"}],\"features\":{\"example_generated\":[],\"serde\":[\"serde_core\"],\"std\":[]}}", "block-buffer_0.10.4": "{\"dependencies\":[{\"name\":\"generic-array\",\"req\":\"^0.14\"}],\"features\":{}}", "block-padding_0.3.3": "{\"dependencies\":[{\"name\":\"generic-array\",\"req\":\"^0.14\"}],\"features\":{\"std\":[]}}", + "block2_0.6.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"}],\"features\":{\"alloc\":[],\"compiler-rt\":[\"objc2/unstable-compiler-rt\"],\"default\":[\"std\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\"],\"std\":[\"alloc\"],\"unstable-coerce-pointee\":[],\"unstable-objfw\":[],\"unstable-private\":[],\"unstable-winobjc\":[\"gnustep-1-8\"]}}", "blocking_1.6.2": "{\"dependencies\":[{\"name\":\"async-channel\",\"req\":\"^2.0.0\"},{\"name\":\"async-task\",\"req\":\"^4.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-io\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"piper\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.37\"}],\"features\":{}}", "borsh_1.6.0": "{\"dependencies\":[{\"name\":\"ascii\",\"optional\":true,\"req\":\"^1.1\"},{\"name\":\"borsh-derive\",\"optional\":true,\"req\":\"~1.6.0\"},{\"name\":\"bson\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"cfg_aliases\",\"req\":\"^0.2.1\"},{\"name\":\"hashbrown\",\"optional\":true,\"req\":\">=0.11, <0.16.0\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.29.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"}],\"features\":{\"de_strict_order\":[],\"default\":[\"std\"],\"derive\":[\"borsh-derive\"],\"rc\":[],\"std\":[],\"unstable__schema\":[\"derive\",\"borsh-derive/schema\"]}}", - "bstr_1.12.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.7.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"dfa-search\"],\"name\":\"regex-automata\",\"optional\":true,\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.85\"},{\"kind\":\"dev\",\"name\":\"ucd-parse\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"unicode-segmentation\",\"req\":\"^1.2.1\"}],\"features\":{\"alloc\":[\"memchr/alloc\",\"serde?/alloc\"],\"default\":[\"std\",\"unicode\"],\"serde\":[\"dep:serde\"],\"std\":[\"alloc\",\"memchr/std\",\"serde?/std\"],\"unicode\":[\"dep:regex-automata\"]}}", - "bumpalo_3.19.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.8\"},{\"kind\":\"dev\",\"name\":\"blink-alloc\",\"req\":\"=0.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.171\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.197\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.115\"}],\"features\":{\"allocator_api\":[],\"bench_allocator_api\":[\"allocator_api\",\"blink-alloc/nightly\"],\"boxed\":[],\"collections\":[],\"default\":[],\"serde\":[\"dep:serde\"],\"std\":[]}}", - "bytemuck_1.23.1": "{\"dependencies\":[{\"name\":\"bytemuck_derive\",\"optional\":true,\"req\":\"^1.4.1\"}],\"features\":{\"aarch64_simd\":[],\"align_offset\":[],\"alloc_uninit\":[],\"avx512_simd\":[],\"const_zeroed\":[],\"derive\":[\"bytemuck_derive\"],\"extern_crate_alloc\":[],\"extern_crate_std\":[\"extern_crate_alloc\"],\"impl_core_error\":[],\"latest_stable_rust\":[\"aarch64_simd\",\"avx512_simd\",\"align_offset\",\"alloc_uninit\",\"const_zeroed\",\"derive\",\"impl_core_error\",\"min_const_generics\",\"must_cast\",\"must_cast_extra\",\"pod_saturating\",\"track_caller\",\"transparentwrapper_extra\",\"wasm_simd\",\"zeroable_atomics\",\"zeroable_maybe_uninit\",\"zeroable_unwind_fn\"],\"min_const_generics\":[],\"must_cast\":[],\"must_cast_extra\":[\"must_cast\"],\"nightly_docs\":[],\"nightly_float\":[],\"nightly_portable_simd\":[],\"nightly_stdsimd\":[],\"pod_saturating\":[],\"track_caller\":[],\"transparentwrapper_extra\":[],\"unsound_ptr_pod_impl\":[],\"wasm_simd\":[],\"zeroable_atomics\":[],\"zeroable_maybe_uninit\":[],\"zeroable_unwind_fn\":[]}}", + "bstr_1.12.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.7.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"dfa-search\"],\"name\":\"regex-automata\",\"optional\":true,\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.85\"},{\"kind\":\"dev\",\"name\":\"ucd-parse\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"unicode-segmentation\",\"req\":\"^1.2.1\"}],\"features\":{\"alloc\":[\"memchr/alloc\",\"serde?/alloc\"],\"default\":[\"std\",\"unicode\"],\"serde\":[\"dep:serde\"],\"std\":[\"alloc\",\"memchr/std\",\"serde?/std\"],\"unicode\":[\"dep:regex-automata\"]}}", + "bumpalo_3.19.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.8\"},{\"kind\":\"dev\",\"name\":\"blink-alloc\",\"req\":\"=0.4.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"=1.10.0\"},{\"kind\":\"dev\",\"name\":\"rayon-core\",\"req\":\"=1.12.1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.171\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.197\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.115\"}],\"features\":{\"allocator_api\":[],\"bench_allocator_api\":[\"allocator_api\",\"blink-alloc/nightly\"],\"boxed\":[],\"collections\":[],\"default\":[],\"serde\":[\"dep:serde\"],\"std\":[]}}", + "bytemuck_1.25.0": "{\"dependencies\":[{\"name\":\"bytemuck_derive\",\"optional\":true,\"req\":\"^1.10.2\"},{\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.22\"}],\"features\":{\"aarch64_simd\":[],\"align_offset\":[],\"alloc_uninit\":[],\"avx512_simd\":[],\"const_zeroed\":[],\"derive\":[\"bytemuck_derive\"],\"extern_crate_alloc\":[],\"extern_crate_std\":[\"extern_crate_alloc\"],\"impl_core_error\":[],\"latest_stable_rust\":[\"aarch64_simd\",\"avx512_simd\",\"align_offset\",\"alloc_uninit\",\"const_zeroed\",\"derive\",\"impl_core_error\",\"min_const_generics\",\"must_cast\",\"must_cast_extra\",\"pod_saturating\",\"track_caller\",\"transparentwrapper_extra\",\"wasm_simd\",\"zeroable_atomics\",\"zeroable_maybe_uninit\",\"zeroable_unwind_fn\"],\"min_const_generics\":[],\"must_cast\":[],\"must_cast_extra\":[\"must_cast\"],\"nightly_docs\":[],\"nightly_float\":[],\"nightly_portable_simd\":[\"rustversion\"],\"nightly_stdsimd\":[],\"pod_saturating\":[],\"track_caller\":[],\"transparentwrapper_extra\":[],\"unsound_ptr_pod_impl\":[],\"wasm_simd\":[],\"zeroable_atomics\":[],\"zeroable_maybe_uninit\":[],\"zeroable_unwind_fn\":[]}}", "byteorder-lite_0.1.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "byteorder_1.5.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"std\"],\"i128\":[],\"std\":[]}}", - "bytes_1.10.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"extra-platforms\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.60\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "bytes_1.11.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"extra-platforms\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.60\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "bytestring_1.5.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{\"serde\":[\"dep:serde_core\"]}}", + "bzip2-sys_0.1.13+1.0.8": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"}],\"features\":{\"__disabled\":[],\"static\":[]}}", + "bzip2_0.5.2": "{\"dependencies\":[{\"name\":\"bzip2-sys\",\"optional\":true,\"req\":\"^0.1.13\"},{\"default_features\":false,\"features\":[\"rust-allocator\",\"semver-prefix\"],\"name\":\"libbz2-rs-sys\",\"optional\":true,\"req\":\"^0.1.3\"},{\"features\":[\"quickcheck1\"],\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5.4\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"default\":[\"dep:bzip2-sys\"],\"libbz2-rs-sys\":[\"dep:libbz2-rs-sys\",\"bzip2-sys?/__disabled\"],\"static\":[\"bzip2-sys?/static\"]}}", "cassowary_0.3.0": "{\"dependencies\":[],\"features\":{}}", "castaway_0.2.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\"},{\"name\":\"rustversion\",\"req\":\"^1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "cbc_0.1.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"aes\",\"req\":\"^0.8\"},{\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3.3\"}],\"features\":{\"alloc\":[\"cipher/alloc\"],\"block-padding\":[\"cipher/block-padding\"],\"default\":[\"block-padding\"],\"std\":[\"cipher/std\",\"alloc\"],\"zeroize\":[\"cipher/zeroize\"]}}", - "cc_1.2.52": "{\"dependencies\":[{\"name\":\"find-msvc-tools\",\"req\":\"^0.1.7\"},{\"default_features\":false,\"name\":\"jobserver\",\"optional\":true,\"req\":\"^0.1.30\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(unix)\"},{\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"jobserver\":[],\"parallel\":[\"dep:libc\",\"dep:jobserver\"]}}", + "cc_1.2.55": "{\"dependencies\":[{\"name\":\"find-msvc-tools\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"jobserver\",\"optional\":true,\"req\":\"^0.1.30\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(unix)\"},{\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"jobserver\":[],\"parallel\":[\"dep:libc\",\"dep:jobserver\"]}}", "cesu8_1.1.0": "{\"dependencies\":[],\"features\":{\"unstable\":[]}}", "cexpr_0.6.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"clang-sys\",\"req\":\">=0.13.0, <0.29.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7\"}],\"features\":{}}", - "cfg-if_1.0.1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"rustc-dep-of-std\":[\"core\"]}}", + "cfg-if_1.0.4": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"rustc-dep-of-std\":[\"core\"]}}", "cfg_aliases_0.1.1": "{\"dependencies\":[],\"features\":{}}", "cfg_aliases_0.2.1": "{\"dependencies\":[],\"features\":{}}", + "chacha20_0.9.1": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"cipher\",\"req\":\"^0.4.4\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"cipher\",\"req\":\"^0.4.4\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3.3\"}],\"features\":{\"std\":[\"cipher/std\"],\"zeroize\":[\"cipher/zeroize\"]}}", + "chacha20poly1305_0.10.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aead\",\"req\":\"^0.5\"},{\"default_features\":false,\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"aead\",\"req\":\"^0.5\"},{\"features\":[\"zeroize\"],\"name\":\"chacha20\",\"req\":\"^0.9\"},{\"name\":\"cipher\",\"req\":\"^0.4\"},{\"name\":\"poly1305\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"zeroize\",\"req\":\"^1.5\"}],\"features\":{\"alloc\":[\"aead/alloc\"],\"default\":[\"alloc\",\"getrandom\"],\"getrandom\":[\"aead/getrandom\",\"rand_core\"],\"heapless\":[\"aead/heapless\"],\"rand_core\":[\"aead/rand_core\"],\"reduced-round\":[],\"std\":[\"aead/std\",\"alloc\"],\"stream\":[\"aead/stream\"]}}", "chardetng_0.1.17": "{\"dependencies\":[{\"name\":\"arrayvec\",\"optional\":true,\"req\":\"^0.5.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"detone\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"encoding_rs\",\"req\":\"^0.8.29\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.2.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.3.0\"}],\"features\":{\"multithreading\":[\"rayon\",\"arrayvec\"],\"testing-only-no-semver-guarantees-do-not-use\":[]}}", "chrono_0.4.43": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.0\"},{\"name\":\"defmt\",\"optional\":true,\"req\":\"^1.0.1\"},{\"features\":[\"fallback\"],\"name\":\"iana-time-zone\",\"optional\":true,\"req\":\"^0.1.45\",\"target\":\"cfg(unix)\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"pure-rust-locales\",\"optional\":true,\"req\":\"^0.8.2\"},{\"default_features\":false,\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.7.43\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.99\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.6.1\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"windows-bindgen\",\"req\":\"^0.66\"},{\"name\":\"windows-link\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_bench\":[],\"alloc\":[],\"clock\":[\"winapi\",\"iana-time-zone\",\"now\"],\"core-error\":[],\"default\":[\"clock\",\"std\",\"oldtime\",\"wasmbind\"],\"defmt\":[\"dep:defmt\",\"pure-rust-locales?/defmt\"],\"libc\":[],\"now\":[\"std\"],\"oldtime\":[],\"rkyv\":[\"dep:rkyv\",\"rkyv/size_32\"],\"rkyv-16\":[\"dep:rkyv\",\"rkyv?/size_16\"],\"rkyv-32\":[\"dep:rkyv\",\"rkyv?/size_32\"],\"rkyv-64\":[\"dep:rkyv\",\"rkyv?/size_64\"],\"rkyv-validation\":[\"rkyv?/validation\"],\"std\":[\"alloc\"],\"unstable-locales\":[\"pure-rust-locales\"],\"wasmbind\":[\"wasm-bindgen\",\"js-sys\"],\"winapi\":[\"windows-link\"]}}", "chunked_transfer_1.5.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"}],\"features\":{}}", "cipher_0.4.4": "{\"dependencies\":[{\"name\":\"blobby\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"crypto-common\",\"req\":\"^0.1.6\"},{\"name\":\"inout\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.5\"}],\"features\":{\"alloc\":[],\"block-padding\":[\"inout/block-padding\"],\"dev\":[\"blobby\"],\"rand_core\":[\"crypto-common/rand_core\"],\"std\":[\"alloc\",\"crypto-common/std\",\"inout/std\"]}}", "clang-sys_1.8.1": "{\"dependencies\":[{\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"build\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.39\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\">=3.0.0, <3.7.0\"}],\"features\":{\"clang_10_0\":[\"clang_9_0\"],\"clang_11_0\":[\"clang_10_0\"],\"clang_12_0\":[\"clang_11_0\"],\"clang_13_0\":[\"clang_12_0\"],\"clang_14_0\":[\"clang_13_0\"],\"clang_15_0\":[\"clang_14_0\"],\"clang_16_0\":[\"clang_15_0\"],\"clang_17_0\":[\"clang_16_0\"],\"clang_18_0\":[\"clang_17_0\"],\"clang_3_5\":[],\"clang_3_6\":[\"clang_3_5\"],\"clang_3_7\":[\"clang_3_6\"],\"clang_3_8\":[\"clang_3_7\"],\"clang_3_9\":[\"clang_3_8\"],\"clang_4_0\":[\"clang_3_9\"],\"clang_5_0\":[\"clang_4_0\"],\"clang_6_0\":[\"clang_5_0\"],\"clang_7_0\":[\"clang_6_0\"],\"clang_8_0\":[\"clang_7_0\"],\"clang_9_0\":[\"clang_8_0\"],\"libcpp\":[],\"runtime\":[\"libloading\"],\"static\":[]}}", - "clap_4.5.54": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"clap-cargo\",\"req\":\"^0.15.0\"},{\"default_features\":false,\"name\":\"clap_builder\",\"req\":\"=4.5.54\"},{\"name\":\"clap_derive\",\"optional\":true,\"req\":\"=4.5.49\"},{\"kind\":\"dev\",\"name\":\"jiff\",\"req\":\"^0.2.3\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"semver\",\"req\":\"^1.0.26\"},{\"kind\":\"dev\",\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"features\":[\"term-svg\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.16\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"features\":[\"color-auto\",\"diff\",\"examples\"],\"kind\":\"dev\",\"name\":\"trycmd\",\"req\":\"^0.15.3\"}],\"features\":{\"cargo\":[\"clap_builder/cargo\"],\"color\":[\"clap_builder/color\"],\"debug\":[\"clap_builder/debug\",\"clap_derive?/debug\"],\"default\":[\"std\",\"color\",\"help\",\"usage\",\"error-context\",\"suggestions\"],\"deprecated\":[\"clap_builder/deprecated\",\"clap_derive?/deprecated\"],\"derive\":[\"dep:clap_derive\"],\"env\":[\"clap_builder/env\"],\"error-context\":[\"clap_builder/error-context\"],\"help\":[\"clap_builder/help\"],\"std\":[\"clap_builder/std\"],\"string\":[\"clap_builder/string\"],\"suggestions\":[\"clap_builder/suggestions\"],\"unicode\":[\"clap_builder/unicode\"],\"unstable-derive-ui-tests\":[],\"unstable-doc\":[\"clap_builder/unstable-doc\",\"derive\"],\"unstable-ext\":[\"clap_builder/unstable-ext\"],\"unstable-markdown\":[\"clap_derive/unstable-markdown\"],\"unstable-styles\":[\"clap_builder/unstable-styles\"],\"unstable-v5\":[\"clap_builder/unstable-v5\",\"clap_derive?/unstable-v5\",\"deprecated\"],\"usage\":[\"clap_builder/usage\"],\"wrap_help\":[\"clap_builder/wrap_help\"]}}", - "clap_builder_4.5.54": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.7\"},{\"name\":\"anstyle\",\"req\":\"^1.0.8\"},{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.73\"},{\"name\":\"clap_lex\",\"req\":\"^0.7.4\"},{\"kind\":\"dev\",\"name\":\"color-print\",\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.16\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"name\":\"strsim\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"unic-emoji-char\",\"req\":\"^0.9.0\"},{\"name\":\"unicase\",\"optional\":true,\"req\":\"^2.6.0\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"cargo\":[],\"color\":[\"dep:anstream\"],\"debug\":[\"dep:backtrace\"],\"default\":[\"std\",\"color\",\"help\",\"usage\",\"error-context\",\"suggestions\"],\"deprecated\":[],\"env\":[],\"error-context\":[],\"help\":[],\"std\":[\"anstyle/std\"],\"string\":[],\"suggestions\":[\"dep:strsim\",\"error-context\"],\"unicode\":[\"dep:unicode-width\",\"dep:unicase\"],\"unstable-doc\":[\"cargo\",\"wrap_help\",\"env\",\"unicode\",\"string\",\"unstable-ext\"],\"unstable-ext\":[],\"unstable-styles\":[\"color\"],\"unstable-v5\":[\"deprecated\"],\"usage\":[],\"wrap_help\":[\"help\",\"dep:terminal_size\"]}}", - "clap_complete_4.5.64": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"clap\",\"req\":\"^4.5.20\"},{\"default_features\":false,\"features\":[\"std\",\"derive\",\"help\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.5.20\"},{\"name\":\"clap_lex\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"completest\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"completest-pty\",\"optional\":true,\"req\":\"^0.5.5\"},{\"name\":\"is_executable\",\"optional\":true,\"req\":\"^1.0.1\"},{\"name\":\"shlex\",\"optional\":true,\"req\":\"^1.3.0\"},{\"features\":[\"diff\",\"dir\",\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"features\":[\"color-auto\",\"diff\",\"examples\"],\"kind\":\"dev\",\"name\":\"trycmd\",\"req\":\"^0.15.1\"}],\"features\":{\"debug\":[\"clap/debug\"],\"default\":[],\"unstable-doc\":[\"unstable-dynamic\"],\"unstable-dynamic\":[\"dep:clap_lex\",\"dep:shlex\",\"dep:is_executable\",\"clap/unstable-ext\"],\"unstable-shell-tests\":[\"dep:completest\",\"dep:completest-pty\"]}}", - "clap_derive_4.5.49": "{\"dependencies\":[{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.10\"},{\"name\":\"heck\",\"req\":\"^0.5.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.69\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.13.0\"},{\"name\":\"quote\",\"req\":\"^1.0.9\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.8\"}],\"features\":{\"debug\":[],\"default\":[],\"deprecated\":[],\"raw-deprecated\":[\"deprecated\"],\"unstable-markdown\":[\"dep:pulldown-cmark\",\"dep:anstyle\"],\"unstable-v5\":[\"deprecated\"]}}", - "clap_lex_0.7.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"}],\"features\":{}}", + "clap_4.5.56": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"clap-cargo\",\"req\":\"^0.15.0\"},{\"default_features\":false,\"name\":\"clap_builder\",\"req\":\"=4.5.56\"},{\"name\":\"clap_derive\",\"optional\":true,\"req\":\"=4.5.55\"},{\"kind\":\"dev\",\"name\":\"jiff\",\"req\":\"^0.2.3\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"semver\",\"req\":\"^1.0.26\"},{\"kind\":\"dev\",\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"features\":[\"term-svg\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.16\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"features\":[\"color-auto\",\"diff\",\"examples\"],\"kind\":\"dev\",\"name\":\"trycmd\",\"req\":\"^0.15.3\"}],\"features\":{\"cargo\":[\"clap_builder/cargo\"],\"color\":[\"clap_builder/color\"],\"debug\":[\"clap_builder/debug\",\"clap_derive?/debug\"],\"default\":[\"std\",\"color\",\"help\",\"usage\",\"error-context\",\"suggestions\"],\"deprecated\":[\"clap_builder/deprecated\",\"clap_derive?/deprecated\"],\"derive\":[\"dep:clap_derive\"],\"env\":[\"clap_builder/env\"],\"error-context\":[\"clap_builder/error-context\"],\"help\":[\"clap_builder/help\"],\"std\":[\"clap_builder/std\"],\"string\":[\"clap_builder/string\"],\"suggestions\":[\"clap_builder/suggestions\"],\"unicode\":[\"clap_builder/unicode\"],\"unstable-derive-ui-tests\":[],\"unstable-doc\":[\"clap_builder/unstable-doc\",\"derive\"],\"unstable-ext\":[\"clap_builder/unstable-ext\"],\"unstable-markdown\":[\"clap_derive/unstable-markdown\"],\"unstable-styles\":[\"clap_builder/unstable-styles\"],\"unstable-v5\":[\"clap_builder/unstable-v5\",\"clap_derive?/unstable-v5\",\"deprecated\"],\"usage\":[\"clap_builder/usage\"],\"wrap_help\":[\"clap_builder/wrap_help\"]}}", + "clap_builder_4.5.56": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.7\"},{\"name\":\"anstyle\",\"req\":\"^1.0.8\"},{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.73\"},{\"name\":\"clap_lex\",\"req\":\"^0.7.4\"},{\"kind\":\"dev\",\"name\":\"color-print\",\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.16\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"name\":\"strsim\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"unic-emoji-char\",\"req\":\"^0.9.0\"},{\"name\":\"unicase\",\"optional\":true,\"req\":\"^2.6.0\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"cargo\":[],\"color\":[\"dep:anstream\"],\"debug\":[\"dep:backtrace\"],\"default\":[\"std\",\"color\",\"help\",\"usage\",\"error-context\",\"suggestions\"],\"deprecated\":[],\"env\":[],\"error-context\":[],\"help\":[],\"std\":[\"anstyle/std\"],\"string\":[],\"suggestions\":[\"dep:strsim\",\"error-context\"],\"unicode\":[\"dep:unicode-width\",\"dep:unicase\"],\"unstable-doc\":[\"cargo\",\"wrap_help\",\"env\",\"unicode\",\"string\",\"unstable-ext\"],\"unstable-ext\":[],\"unstable-styles\":[\"color\"],\"unstable-v5\":[\"deprecated\"],\"usage\":[],\"wrap_help\":[\"help\",\"dep:terminal_size\"]}}", + "clap_complete_4.5.65": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"clap\",\"req\":\"^4.5.20\"},{\"default_features\":false,\"features\":[\"std\",\"derive\",\"help\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.5.20\"},{\"name\":\"clap_lex\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"completest\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"completest-pty\",\"optional\":true,\"req\":\"^0.5.5\"},{\"name\":\"is_executable\",\"optional\":true,\"req\":\"^1.0.1\"},{\"name\":\"shlex\",\"optional\":true,\"req\":\"^1.3.0\"},{\"features\":[\"diff\",\"dir\",\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"features\":[\"color-auto\",\"diff\",\"examples\"],\"kind\":\"dev\",\"name\":\"trycmd\",\"req\":\"^0.15.1\"}],\"features\":{\"debug\":[\"clap/debug\"],\"default\":[],\"unstable-doc\":[\"unstable-dynamic\"],\"unstable-dynamic\":[\"dep:clap_lex\",\"dep:shlex\",\"dep:is_executable\",\"clap/unstable-ext\"],\"unstable-shell-tests\":[\"dep:completest\",\"dep:completest-pty\"]}}", + "clap_derive_4.5.55": "{\"dependencies\":[{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.10\"},{\"name\":\"heck\",\"req\":\"^0.5.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.69\"},{\"default_features\":false,\"name\":\"pulldown-cmark\",\"optional\":true,\"req\":\"^0.13.0\"},{\"name\":\"quote\",\"req\":\"^1.0.9\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.8\"}],\"features\":{\"debug\":[],\"default\":[],\"deprecated\":[],\"raw-deprecated\":[\"deprecated\"],\"unstable-markdown\":[\"dep:pulldown-cmark\",\"dep:anstyle\"],\"unstable-v5\":[\"deprecated\"]}}", + "clap_lex_0.7.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"}],\"features\":{}}", "clipboard-win_5.4.1": "{\"dependencies\":[{\"name\":\"error-code\",\"req\":\"^3\",\"target\":\"cfg(windows)\"},{\"name\":\"windows-win\",\"optional\":true,\"req\":\"^3\",\"target\":\"cfg(windows)\"}],\"features\":{\"monitor\":[\"windows-win\"],\"std\":[\"error-code/std\"]}}", "cmake_0.1.57": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.2.46\"}],\"features\":{}}", "cmp_any_0.8.1": "{\"dependencies\":[],\"features\":{}}", @@ -655,8 +667,10 @@ "const-oid_0.9.6": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"}],\"features\":{\"db\":[],\"std\":[]}}", "const_format_0.2.35": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"arrayvec\",\"req\":\"^0.7.0\"},{\"name\":\"const_format_proc_macros\",\"req\":\"=0.2.34\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^1.3.5\"},{\"default_features\":false,\"name\":\"konst\",\"optional\":true,\"req\":\"^0.2.13\"}],\"features\":{\"__debug\":[\"const_format_proc_macros/debug\"],\"__docsrs\":[],\"__inline_const_pat_tests\":[\"__test\",\"fmt\"],\"__only_new_tests\":[\"__test\"],\"__test\":[],\"all\":[\"fmt\",\"derive\",\"rust_1_64\",\"assert\"],\"assert\":[\"assertc\"],\"assertc\":[\"fmt\",\"assertcp\"],\"assertcp\":[\"rust_1_51\"],\"const_generics\":[\"rust_1_51\"],\"constant_time_as_str\":[\"fmt\"],\"default\":[],\"derive\":[\"fmt\",\"const_format_proc_macros/derive\"],\"fmt\":[\"rust_1_83\"],\"more_str_macros\":[\"rust_1_64\"],\"nightly_const_generics\":[\"const_generics\"],\"rust_1_51\":[],\"rust_1_64\":[\"rust_1_51\",\"konst\",\"konst/rust_1_64\"],\"rust_1_83\":[\"rust_1_64\"]}}", "const_format_proc_macros_0.2.34": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^1.3.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.19\"},{\"name\":\"quote\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\"],\"name\":\"syn\",\"optional\":true,\"req\":\"^1.0.38\"},{\"name\":\"unicode-xid\",\"req\":\"^0.2\"}],\"features\":{\"all\":[\"derive\"],\"debug\":[\"syn/extra-traits\"],\"default\":[],\"derive\":[\"syn\",\"syn/derive\",\"syn/printing\"]}}", + "constant_time_eq_0.3.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"count_instructions\",\"req\":\"^0.1.3\"},{\"features\":[\"cargo_bench_support\",\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"}],\"features\":{\"count_instructions_test\":[]}}", "convert_case_0.10.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.9.0\"}],\"features\":{}}", "convert_case_0.6.0": "{\"dependencies\":[{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.18.0\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.18.0\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.9.0\"}],\"features\":{\"random\":[\"rand\"]}}", + "cookie-factory_0.3.3": "{\"dependencies\":[{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.9.0\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.16\"},{\"kind\":\"dev\",\"name\":\"maplit\",\"req\":\"^1.0\"}],\"features\":{\"async\":[\"futures\"],\"default\":[\"std\",\"async\"],\"std\":[]}}", "core-foundation-sys_0.8.7": "{\"dependencies\":[],\"features\":{\"default\":[\"link\"],\"link\":[],\"mac_os_10_7_support\":[],\"mac_os_10_8_features\":[]}}", "core-foundation_0.10.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"default\":[\"link\"],\"link\":[\"core-foundation-sys/link\"],\"mac_os_10_7_support\":[\"core-foundation-sys/mac_os_10_7_support\"],\"mac_os_10_8_features\":[\"core-foundation-sys/mac_os_10_8_features\"],\"with-uuid\":[\"dep:uuid\"]}}", "core-foundation_0.9.4": "{\"dependencies\":[{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"default\":[\"link\"],\"link\":[\"core-foundation-sys/link\"],\"mac_os_10_7_support\":[\"core-foundation-sys/mac_os_10_7_support\"],\"mac_os_10_8_features\":[\"core-foundation-sys/mac_os_10_8_features\"],\"with-chrono\":[\"chrono\"],\"with-uuid\":[\"uuid\"]}}", @@ -672,31 +686,32 @@ "crossbeam-utils_0.8.21": "{\"dependencies\":[{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7.1\",\"target\":\"cfg(crossbeam_loom)\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"default\":[\"std\"],\"nightly\":[],\"std\":[]}}", "crossterm_winapi_0.9.1": "{\"dependencies\":[{\"features\":[\"winbase\",\"consoleapi\",\"processenv\",\"handleapi\",\"synchapi\",\"impl-default\"],\"name\":\"winapi\",\"req\":\"^0.3.8\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "crunchy_0.2.4": "{\"dependencies\":[],\"features\":{\"default\":[\"limit_128\"],\"limit_1024\":[],\"limit_128\":[],\"limit_2048\":[],\"limit_256\":[],\"limit_512\":[],\"limit_64\":[],\"std\":[]}}", - "crypto-common_0.1.6": "{\"dependencies\":[{\"features\":[\"more_lengths\"],\"name\":\"generic-array\",\"req\":\"^0.14.4\"},{\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"typenum\",\"req\":\"^1.14\"}],\"features\":{\"getrandom\":[\"rand_core/getrandom\"],\"std\":[]}}", + "crypto-common_0.1.7": "{\"dependencies\":[{\"features\":[\"more_lengths\"],\"name\":\"generic-array\",\"req\":\"=0.14.7\"},{\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"typenum\",\"req\":\"^1.14\"}],\"features\":{\"getrandom\":[\"rand_core/getrandom\"],\"std\":[]}}", "csv-core_0.1.13": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"arrayvec\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2\"}],\"features\":{\"default\":[],\"libc\":[\"memchr/libc\"]}}", "csv_1.4.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\",\"serde\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.7.0\"},{\"name\":\"csv-core\",\"req\":\"^0.1.11\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"name\":\"ryu\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"name\":\"serde_core\",\"req\":\"^1.0.221\"}],\"features\":{}}", "ctor-proc-macro_0.0.7": "{\"dependencies\":[],\"features\":{\"default\":[]}}", "ctor_0.1.26": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"libc-print\",\"req\":\"^0.1.20\"},{\"name\":\"quote\",\"req\":\"^1.0.20\"},{\"default_features\":false,\"features\":[\"full\",\"parsing\",\"printing\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^1.0.98\"}],\"features\":{}}", "ctor_0.6.3": "{\"dependencies\":[{\"name\":\"ctor-proc-macro\",\"optional\":true,\"req\":\"=0.0.7\"},{\"default_features\":false,\"name\":\"dtor\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"libc-print\",\"req\":\"^0.1.20\"}],\"features\":{\"__no_warn_on_missing_unsafe\":[\"dtor?/__no_warn_on_missing_unsafe\"],\"default\":[\"dtor\",\"proc_macro\",\"__no_warn_on_missing_unsafe\"],\"dtor\":[\"dep:dtor\"],\"proc_macro\":[\"dep:ctor-proc-macro\",\"dtor?/proc_macro\"],\"used_linker\":[\"dtor?/used_linker\"]}}", - "darling_0.20.11": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.20.11\"},{\"name\":\"darling_macro\",\"req\":\"=0.20.11\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\",\"target\":\"cfg(compiletests)\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.15\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\",\"target\":\"cfg(compiletests)\"}],\"features\":{\"default\":[\"suggestions\"],\"diagnostics\":[\"darling_core/diagnostics\"],\"suggestions\":[\"darling_core/suggestions\"]}}", + "curve25519-dalek-derive_0.1.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.66\"},{\"name\":\"quote\",\"req\":\"^1.0.31\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.27\"}],\"features\":{}}", + "curve25519-dalek_4.1.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1\"},{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2.6\",\"target\":\"cfg(target_arch = \\\"x86_64\\\")\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"curve25519-dalek-derive\",\"req\":\"^0.1\",\"target\":\"cfg(all(not(curve25519_dalek_backend = \\\"fiat\\\"), not(curve25519_dalek_backend = \\\"serial\\\"), target_arch = \\\"x86_64\\\"))\"},{\"default_features\":false,\"name\":\"digest\",\"optional\":true,\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"ff\",\"optional\":true,\"req\":\"^0.13\"},{\"default_features\":false,\"name\":\"fiat-crypto\",\"req\":\"^0.2.1\",\"target\":\"cfg(curve25519_dalek_backend = \\\"fiat\\\")\"},{\"default_features\":false,\"name\":\"group\",\"optional\":true,\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6.4\"},{\"default_features\":false,\"features\":[\"getrandom\"],\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.3.0\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"zeroize?/alloc\"],\"default\":[\"alloc\",\"precomputed-tables\",\"zeroize\"],\"group\":[\"dep:group\",\"rand_core\"],\"group-bits\":[\"group\",\"ff/bits\"],\"legacy_compatibility\":[],\"precomputed-tables\":[]}}", "darling_0.21.3": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.21.3\"},{\"name\":\"darling_macro\",\"req\":\"=0.21.3\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\",\"target\":\"cfg(compiletests)\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.15\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\",\"target\":\"cfg(compiletests)\"}],\"features\":{\"default\":[\"suggestions\"],\"diagnostics\":[\"darling_core/diagnostics\"],\"serde\":[\"darling_core/serde\"],\"suggestions\":[\"darling_core/suggestions\"]}}", "darling_0.23.0": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.23.0\"},{\"name\":\"darling_macro\",\"req\":\"=0.23.0\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\",\"target\":\"cfg(compiletests)\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.15\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\",\"target\":\"cfg(compiletests)\"}],\"features\":{\"default\":[\"suggestions\"],\"diagnostics\":[\"darling_core/diagnostics\"],\"serde\":[\"darling_core/serde\"],\"suggestions\":[\"darling_core/suggestions\"]}}", - "darling_core_0.20.11": "{\"dependencies\":[{\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"name\":\"ident_case\",\"req\":\"^1.0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"strsim\",\"optional\":true,\"req\":\"^0.11.1\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{\"diagnostics\":[],\"suggestions\":[\"strsim\"]}}", "darling_core_0.21.3": "{\"dependencies\":[{\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"name\":\"ident_case\",\"req\":\"^1.0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.210\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.140\"},{\"name\":\"strsim\",\"optional\":true,\"req\":\"^0.11.1\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{\"diagnostics\":[],\"suggestions\":[\"strsim\"]}}", "darling_core_0.23.0": "{\"dependencies\":[{\"name\":\"ident_case\",\"req\":\"^1.0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.210\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.140\"},{\"name\":\"strsim\",\"optional\":true,\"req\":\"^0.11.1\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{\"diagnostics\":[],\"suggestions\":[\"strsim\"]}}", - "darling_macro_0.20.11": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.20.11\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", "darling_macro_0.21.3": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.21.3\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", "darling_macro_0.23.0": "{\"dependencies\":[{\"name\":\"darling_core\",\"req\":\"=0.23.0\"},{\"name\":\"quote\",\"req\":\"^1.0.18\"},{\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", "data-encoding_2.10.0": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "dbus-secret-service_4.1.0": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"std\"],\"name\":\"block-padding\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"block-padding\",\"alloc\"],\"name\":\"cbc\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"dbus\",\"req\":\"^0.9\"},{\"name\":\"fastrand\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"hkdf\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"num\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"openssl\",\"optional\":true,\"req\":\"^0.10.55\"},{\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"features\":[\"derive\"],\"name\":\"zeroize\",\"req\":\"^1.8\"}],\"features\":{\"crypto-openssl\":[\"dep:fastrand\",\"dep:num\",\"dep:once_cell\",\"dep:openssl\"],\"crypto-rust\":[\"dep:aes\",\"dep:block-padding\",\"dep:cbc\",\"dep:fastrand\",\"dep:hkdf\",\"dep:num\",\"dep:once_cell\",\"dep:sha2\"],\"vendored\":[\"dbus/vendored\",\"openssl?/vendored\"]}}", - "dbus_0.9.9": "{\"dependencies\":[{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"libc\",\"req\":\"^0.2.66\"},{\"name\":\"libdbus-sys\",\"req\":\"^0.2.6\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\"^0.59.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"futures\":[\"futures-util\",\"futures-channel\"],\"no-string-validation\":[],\"stdfd\":[],\"vendored\":[\"libdbus-sys/vendored\"]}}", + "dbus_0.9.10": "{\"dependencies\":[{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"libc\",\"req\":\"^0.2.66\"},{\"name\":\"libdbus-sys\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\"^0.59.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"futures\":[\"futures-util\",\"futures-channel\"],\"no-string-validation\":[],\"stdfd\":[],\"vendored\":[\"libdbus-sys/vendored\"]}}", "deadpool-runtime_0.1.4": "{\"dependencies\":[{\"features\":[\"unstable\"],\"name\":\"async-std_1\",\"optional\":true,\"package\":\"async-std\",\"req\":\"^1.0\"},{\"features\":[\"time\",\"rt\"],\"name\":\"tokio_1\",\"optional\":true,\"package\":\"tokio\",\"req\":\"^1.0\"}],\"features\":{}}", "deadpool_0.12.3": "{\"dependencies\":[{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.0\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"config\",\"req\":\"^0.15\"},{\"features\":[\"html_reports\",\"async_tokio\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"deadpool-runtime\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"name\":\"lazy_static\",\"req\":\"^1.5.0\"},{\"name\":\"num_cpus\",\"req\":\"^1.11.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1.5\"},{\"features\":[\"macros\",\"rt\",\"rt-multi-thread\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.5.0\"}],\"features\":{\"default\":[\"managed\",\"unmanaged\"],\"managed\":[],\"rt_async-std_1\":[\"deadpool-runtime/async-std_1\"],\"rt_tokio_1\":[\"deadpool-runtime/tokio_1\"],\"unmanaged\":[]}}", "debugid_0.8.0": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.85\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.37\"},{\"name\":\"uuid\",\"req\":\"^1.0.0\"}],\"features\":{}}", "debugserver-types_0.5.0": "{\"dependencies\":[{\"name\":\"schemafy\",\"req\":\"^0.5.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{}}", + "deflate64_0.1.10": "{\"dependencies\":[{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.13.1\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.7.1\"}],\"features\":{}}", "der_0.7.10": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3\"},{\"default_features\":false,\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"const-oid\",\"optional\":true,\"req\":\"^0.9.2\"},{\"name\":\"der_derive\",\"optional\":true,\"req\":\"^0.7.2\"},{\"name\":\"flagset\",\"optional\":true,\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.1\"},{\"features\":[\"alloc\"],\"name\":\"pem-rfc7468\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.4\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.5\"}],\"features\":{\"alloc\":[\"zeroize?/alloc\"],\"arbitrary\":[\"dep:arbitrary\",\"const-oid?/arbitrary\",\"std\"],\"bytes\":[\"dep:bytes\",\"alloc\"],\"derive\":[\"dep:der_derive\"],\"oid\":[\"dep:const-oid\"],\"pem\":[\"dep:pem-rfc7468\",\"alloc\",\"zeroize\"],\"real\":[],\"std\":[\"alloc\"]}}", - "deranged_0.5.4": "{\"dependencies\":[{\"name\":\"deranged-macros\",\"optional\":true,\"req\":\"=0.3.0\"},{\"default_features\":false,\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.15\"},{\"default_features\":false,\"name\":\"powerfmt\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"rand08\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"rand08\",\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"name\":\"rand09\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand09\",\"package\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.86\"}],\"features\":{\"alloc\":[],\"default\":[],\"macros\":[\"dep:deranged-macros\"],\"num\":[\"dep:num-traits\"],\"powerfmt\":[\"dep:powerfmt\"],\"quickcheck\":[\"dep:quickcheck\",\"alloc\"],\"rand\":[\"rand08\",\"rand09\"],\"rand08\":[\"dep:rand08\"],\"rand09\":[\"dep:rand09\"],\"serde\":[\"dep:serde_core\"]}}", + "deranged_0.5.5": "{\"dependencies\":[{\"name\":\"deranged-macros\",\"optional\":true,\"req\":\"=0.3.0\"},{\"default_features\":false,\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.15\"},{\"default_features\":false,\"name\":\"powerfmt\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"rand08\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"rand08\",\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"name\":\"rand09\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand09\",\"package\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.86\"}],\"features\":{\"alloc\":[],\"default\":[],\"macros\":[\"dep:deranged-macros\"],\"num\":[\"dep:num-traits\"],\"powerfmt\":[\"dep:powerfmt\"],\"quickcheck\":[\"dep:quickcheck\",\"alloc\"],\"rand\":[\"rand08\",\"rand09\"],\"rand08\":[\"dep:rand08\"],\"rand09\":[\"dep:rand09\"],\"serde\":[\"dep:serde_core\"]}}", "derivative_2.2.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"visit\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.18, < 1.0.23\"}],\"features\":{\"use_core\":[]}}", + "derive_arbitrary_1.4.2": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"derive\",\"parsing\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "derive_more-impl_1.0.0": "{\"dependencies\":[{\"name\":\"convert_case\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.13.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"syn\",\"req\":\"^2.0.45\"},{\"name\":\"unicode-xid\",\"optional\":true,\"req\":\"^0.2.2\"}],\"features\":{\"add\":[],\"add_assign\":[],\"as_ref\":[\"syn/extra-traits\",\"syn/visit\"],\"constructor\":[],\"debug\":[\"syn/extra-traits\",\"dep:unicode-xid\"],\"default\":[],\"deref\":[],\"deref_mut\":[],\"display\":[\"syn/extra-traits\",\"dep:unicode-xid\"],\"error\":[\"syn/extra-traits\"],\"from\":[\"syn/extra-traits\"],\"from_str\":[],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[],\"index_mut\":[],\"into\":[\"syn/extra-traits\"],\"into_iterator\":[],\"is_variant\":[\"dep:convert_case\"],\"mul\":[\"syn/extra-traits\"],\"mul_assign\":[\"syn/extra-traits\"],\"not\":[\"syn/extra-traits\"],\"sum\":[],\"testing-helpers\":[\"dep:rustc_version\"],\"try_from\":[],\"try_into\":[\"syn/extra-traits\"],\"try_unwrap\":[\"dep:convert_case\"],\"unwrap\":[\"dep:convert_case\"]}}", "derive_more-impl_2.1.1": "{\"dependencies\":[{\"name\":\"convert_case\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4\"},{\"name\":\"syn\",\"req\":\"^2.0.45\"},{\"name\":\"unicode-xid\",\"optional\":true,\"req\":\"^0.2.2\"}],\"features\":{\"add\":[\"syn/extra-traits\",\"syn/visit\"],\"add_assign\":[\"syn/extra-traits\",\"syn/visit\"],\"as_ref\":[\"syn/extra-traits\",\"syn/visit\"],\"constructor\":[],\"debug\":[\"syn/extra-traits\",\"dep:unicode-xid\"],\"default\":[],\"deref\":[],\"deref_mut\":[],\"display\":[\"syn/extra-traits\",\"dep:unicode-xid\",\"dep:convert_case\"],\"eq\":[\"syn/extra-traits\",\"syn/visit\"],\"error\":[\"syn/extra-traits\"],\"from\":[\"syn/extra-traits\"],\"from_str\":[\"syn/full\",\"syn/visit\",\"dep:convert_case\"],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"eq\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[],\"index_mut\":[],\"into\":[\"syn/extra-traits\",\"syn/visit-mut\"],\"into_iterator\":[],\"is_variant\":[\"dep:convert_case\"],\"mul\":[\"syn/extra-traits\",\"syn/visit\"],\"mul_assign\":[\"syn/extra-traits\",\"syn/visit\"],\"not\":[\"syn/extra-traits\"],\"sum\":[],\"testing-helpers\":[\"syn/full\"],\"try_from\":[],\"try_into\":[\"syn/extra-traits\",\"syn/full\",\"syn/visit-mut\"],\"try_unwrap\":[\"dep:convert_case\"],\"unwrap\":[\"dep:convert_case\"]}}", "derive_more_1.0.0": "{\"dependencies\":[{\"name\":\"derive_more-impl\",\"req\":\"=1.0.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.56\"}],\"features\":{\"add\":[\"derive_more-impl/add\"],\"add_assign\":[\"derive_more-impl/add_assign\"],\"as_ref\":[\"derive_more-impl/as_ref\"],\"constructor\":[\"derive_more-impl/constructor\"],\"debug\":[\"derive_more-impl/debug\"],\"default\":[\"std\"],\"deref\":[\"derive_more-impl/deref\"],\"deref_mut\":[\"derive_more-impl/deref_mut\"],\"display\":[\"derive_more-impl/display\"],\"error\":[\"derive_more-impl/error\"],\"from\":[\"derive_more-impl/from\"],\"from_str\":[\"derive_more-impl/from_str\"],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[\"derive_more-impl/index\"],\"index_mut\":[\"derive_more-impl/index_mut\"],\"into\":[\"derive_more-impl/into\"],\"into_iterator\":[\"derive_more-impl/into_iterator\"],\"is_variant\":[\"derive_more-impl/is_variant\"],\"mul\":[\"derive_more-impl/mul\"],\"mul_assign\":[\"derive_more-impl/mul_assign\"],\"not\":[\"derive_more-impl/not\"],\"std\":[],\"sum\":[\"derive_more-impl/sum\"],\"testing-helpers\":[\"derive_more-impl/testing-helpers\",\"dep:rustc_version\"],\"try_from\":[\"derive_more-impl/try_from\"],\"try_into\":[\"derive_more-impl/try_into\"],\"try_unwrap\":[\"derive_more-impl/try_unwrap\"],\"unwrap\":[\"derive_more-impl/unwrap\"]}}", @@ -712,36 +727,35 @@ "dispatch2_0.3.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"alloc\":[],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"block2\",\"libc\",\"objc2\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\"],\"std\":[\"alloc\"]}}", "display_container_0.9.0": "{\"dependencies\":[{\"name\":\"either\",\"req\":\"^1.8\"},{\"name\":\"indenter\",\"req\":\"^0.3.3\"}],\"features\":{}}", "displaydoc_0.2.5": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^0.6.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^1.0.24\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "doc-comment_0.3.3": "{\"dependencies\":[],\"features\":{\"no_core\":[],\"old_macros\":[]}}", "dotenvy_0.15.7": "{\"dependencies\":[{\"name\":\"clap\",\"optional\":true,\"req\":\"^3.2\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.16.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.3.0\"}],\"features\":{\"cli\":[\"clap\"]}}", "downcast-rs_1.2.1": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "dtor-proc-macro_0.0.6": "{\"dependencies\":[],\"features\":{\"default\":[]}}", - "dtor_0.1.0": "{\"dependencies\":[{\"name\":\"dtor-proc-macro\",\"optional\":true,\"req\":\"=0.0.6\"},{\"kind\":\"dev\",\"name\":\"libc-print\",\"req\":\"^0.1.20\"}],\"features\":{\"__no_warn_on_missing_unsafe\":[],\"default\":[\"proc_macro\",\"__no_warn_on_missing_unsafe\"],\"proc_macro\":[\"dep:dtor-proc-macro\"],\"used_linker\":[]}}", + "dtor_0.1.1": "{\"dependencies\":[{\"name\":\"dtor-proc-macro\",\"optional\":true,\"req\":\"=0.0.6\"},{\"kind\":\"dev\",\"name\":\"libc-print\",\"req\":\"^0.1.20\"}],\"features\":{\"__no_warn_on_missing_unsafe\":[],\"default\":[\"proc_macro\",\"__no_warn_on_missing_unsafe\"],\"proc_macro\":[\"dep:dtor-proc-macro\"],\"used_linker\":[]}}", "dunce_1.0.5": "{\"dependencies\":[],\"features\":{}}", "dupe_0.9.1": "{\"dependencies\":[{\"name\":\"dupe_derive\",\"req\":\"=0.9.1\"}],\"features\":{}}", "dupe_derive_0.9.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0.3\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", - "dyn-clone_1.0.19": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.66\"}],\"features\":{}}", + "dyn-clone_1.0.20": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.66\"}],\"features\":{}}", "either_1.15.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.95\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[],\"use_std\":[\"std\"]}}", "ena_0.14.3": "{\"dependencies\":[{\"name\":\"dogged\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"log\",\"req\":\"^0.4\"}],\"features\":{\"bench\":[],\"persistent\":[\"dogged\"]}}", "encode_unicode_1.0.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ascii\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.0\",\"target\":\"cfg(unix)\"},{\"features\":[\"https-native\"],\"kind\":\"dev\",\"name\":\"minreq\",\"req\":\"^2.6\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "encoding_rs_0.8.35": "{\"dependencies\":[{\"name\":\"any_all_workaround\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\"],\"fast-big5-hanzi-encode\":[],\"fast-gb-hanzi-encode\":[],\"fast-hangul-encode\":[],\"fast-hanja-encode\":[],\"fast-kanji-encode\":[],\"fast-legacy-encode\":[\"fast-hangul-encode\",\"fast-hanja-encode\",\"fast-kanji-encode\",\"fast-gb-hanzi-encode\",\"fast-big5-hanzi-encode\"],\"less-slow-big5-hanzi-encode\":[],\"less-slow-gb-hanzi-encode\":[],\"less-slow-kanji-encode\":[],\"simd-accel\":[\"any_all_workaround\"]}}", - "endi_1.1.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "endi_1.1.1": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "endian-type_0.1.2": "{\"dependencies\":[],\"features\":{}}", "endian-type_0.2.0": "{\"dependencies\":[],\"features\":{}}", "enum-as-inner_0.6.1": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "enumflags2_0.7.12": "{\"dependencies\":[{\"name\":\"enumflags2_derive\",\"req\":\"=0.7.12\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"std\":[]}}", "enumflags2_derive_0.7.12": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"printing\",\"derive\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "env-flags_0.1.1": "{\"dependencies\":[],\"features\":{}}", - "env_filter_0.1.3": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.8\"},{\"default_features\":false,\"features\":[\"std\",\"perf\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6\"}],\"features\":{\"default\":[\"regex\"],\"regex\":[\"dep:regex\"]}}", + "env_filter_0.1.4": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.8\"},{\"default_features\":false,\"features\":[\"std\",\"perf\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6\"}],\"features\":{\"default\":[\"regex\"],\"regex\":[\"dep:regex\"]}}", "env_home_0.1.0": "{\"dependencies\":[],\"features\":{}}", "env_logger_0.11.8": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"wincon\"],\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.11\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.6\"},{\"default_features\":false,\"name\":\"env_filter\",\"req\":\"^0.1.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"jiff\",\"optional\":true,\"req\":\"^0.2.3\"},{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.21\"}],\"features\":{\"auto-color\":[\"color\",\"anstream/auto\"],\"color\":[\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"auto-color\",\"humantime\",\"regex\"],\"humantime\":[\"dep:jiff\"],\"kv\":[\"log/kv\"],\"regex\":[\"env_filter/regex\"],\"unstable-kv\":[\"kv\"]}}", "equivalent_1.0.2": "{\"dependencies\":[],\"features\":{}}", "erased-serde_0.3.31": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_cbor\",\"req\":\"^0.11.2\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.99\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.83\"}],\"features\":{\"alloc\":[\"serde/alloc\"],\"default\":[\"std\"],\"std\":[\"serde/std\"],\"unstable-debug\":[]}}", - "errno_0.3.13": "{\"dependencies\":[{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os=\\\"hermit\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os=\\\"wasi\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Diagnostics_Debug\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.60\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"libc/std\"]}}", + "errno_0.3.14": "{\"dependencies\":[{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os=\\\"hermit\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os=\\\"wasi\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Diagnostics_Debug\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"libc/std\"]}}", "error-code_3.3.2": "{\"dependencies\":[],\"features\":{\"std\":[]}}", "etcetera_0.8.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"home\",\"req\":\"^0.5\"},{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\"],\"name\":\"windows-sys\",\"req\":\"^0.48\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "event-listener-strategy_0.5.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"event-listener\",\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.12\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.37\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"loom\":[\"event-listener/loom\"],\"portable-atomic\":[\"event-listener/portable-atomic\"],\"std\":[\"event-listener/std\"]}}", - "event-listener_5.4.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"concurrent-queue\",\"req\":\"^2.4.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1.2.0\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"critical-section\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"parking\",\"optional\":true,\"req\":\"^2.0.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.12\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"portable-atomic-util\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"portable_atomic_crate\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"try-lock\",\"req\":\"^0.2.5\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"loom\":[\"concurrent-queue/loom\",\"parking?/loom\",\"dep:loom\"],\"portable-atomic\":[\"portable-atomic-util\",\"portable_atomic_crate\",\"concurrent-queue/portable-atomic\"],\"std\":[\"concurrent-queue/std\",\"parking\"]}}", + "event-listener_5.4.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"concurrent-queue\",\"req\":\"^2.4.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1.2.0\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"critical-section\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"parking\",\"optional\":true,\"req\":\"^2.0.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.12\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"portable-atomic-util\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"portable_atomic_crate\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"try-lock\",\"req\":\"^0.2.5\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"default\":[\"std\"],\"loom\":[\"concurrent-queue/loom\",\"parking?/loom\",\"dep:loom\"],\"portable-atomic\":[\"portable-atomic-util\",\"portable_atomic_crate\",\"concurrent-queue/portable-atomic\"],\"std\":[\"concurrent-queue/std\",\"parking\"]}}", "eventsource-stream_0.2.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"http\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"nom\",\"req\":\"^7.1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.8\"},{\"features\":[\"stream\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.11\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2.2\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"futures-core/std\",\"nom/std\"]}}", "eyre_0.6.12": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.28\"},{\"kind\":\"dev\",\"name\":\"backtrace\",\"req\":\"^0.3.46\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"indenter\",\"req\":\"^0.3.0\"},{\"name\":\"once_cell\",\"req\":\"^1.18.0\"},{\"default_features\":false,\"name\":\"pyo3\",\"optional\":true,\"req\":\"^0.20\"},{\"default_features\":false,\"features\":[\"auto-initialize\"],\"kind\":\"dev\",\"name\":\"pyo3\",\"req\":\"^0.20\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.19\"}],\"features\":{\"auto-install\":[],\"default\":[\"auto-install\",\"track-caller\"],\"track-caller\":[]}}", "fastrand_2.3.0": "{\"dependencies\":[{\"features\":[\"js\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wyhash\",\"req\":\"^0.5\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"js\":[\"std\",\"getrandom\"],\"std\":[\"alloc\"]}}", @@ -749,13 +763,20 @@ "fax_derive_0.2.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "fd-lock_4.0.4": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.0.0\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.0.8\"},{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_IO\"],\"name\":\"windows-sys\",\"req\":\">=0.52.0, <0.60.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "fdeflate_0.3.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.7.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"name\":\"simd-adler32\",\"req\":\"^0.3.4\"}],\"features\":{}}", + "fiat-crypto_0.2.9": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "filedescriptor_0.8.3": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"winuser\",\"handleapi\",\"fileapi\",\"namedpipeapi\",\"processthreadsapi\",\"winsock2\",\"processenv\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "find-msvc-tools_0.1.7": "{\"dependencies\":[],\"features\":{}}", + "find-crate_0.6.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"semver\",\"req\":\"^0.11\"},{\"name\":\"toml\",\"req\":\"^0.5.2\"}],\"features\":{}}", + "find-msvc-tools_0.1.9": "{\"dependencies\":[],\"features\":{}}", "findshlibs_0.10.2": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.67\"},{\"name\":\"lazy_static\",\"req\":\"^1.4\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"ios\\\"))\"},{\"name\":\"libc\",\"req\":\"^0.2.104\"},{\"features\":[\"psapi\",\"memoryapi\",\"libloaderapi\",\"processthreadsapi\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{}}", - "fixed_decimal_0.7.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand_distr\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"small\"],\"name\":\"ryu\",\"optional\":true,\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"smallvec\",\"req\":\"^1.10.0\"},{\"default_features\":false,\"name\":\"writeable\",\"req\":\"^0.6.0\"}],\"features\":{\"experimental\":[],\"ryu\":[\"dep:ryu\"]}}", + "fixed_decimal_0.7.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"features\":[\"wasm_js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_distr\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"small\"],\"name\":\"ryu\",\"optional\":true,\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"smallvec\",\"req\":\"^1.10.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"writeable\",\"req\":\"^0.6.0\"}],\"features\":{\"experimental\":[],\"ryu\":[\"dep:ryu\"]}}", "fixedbitset_0.4.2": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "flate2_1.1.2": "{\"dependencies\":[{\"name\":\"cloudflare-zlib-sys\",\"optional\":true,\"req\":\"^0.3.5\"},{\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"name\":\"libz-ng-sys\",\"optional\":true,\"req\":\"^1.1.16\"},{\"default_features\":false,\"features\":[\"std\",\"rust-allocator\"],\"name\":\"libz-rs-sys\",\"optional\":true,\"req\":\"^0.5.1\"},{\"default_features\":false,\"name\":\"libz-sys\",\"optional\":true,\"req\":\"^1.1.20\"},{\"default_features\":false,\"features\":[\"with-alloc\"],\"name\":\"miniz_oxide\",\"req\":\"^0.8.5\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"emscripten\\\")))\"},{\"default_features\":false,\"features\":[\"with-alloc\"],\"name\":\"miniz_oxide\",\"optional\":true,\"req\":\"^0.8.5\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"any_impl\":[],\"any_zlib\":[\"any_impl\"],\"cloudflare_zlib\":[\"any_zlib\",\"cloudflare-zlib-sys\"],\"default\":[\"rust_backend\"],\"miniz-sys\":[\"rust_backend\"],\"rust_backend\":[\"miniz_oxide\",\"any_impl\"],\"zlib\":[\"any_zlib\",\"libz-sys\"],\"zlib-default\":[\"any_zlib\",\"libz-sys/default\"],\"zlib-ng\":[\"any_zlib\",\"libz-ng-sys\"],\"zlib-ng-compat\":[\"zlib\",\"libz-sys/zlib-ng\"],\"zlib-rs\":[\"any_zlib\",\"libz-rs-sys\"]}}", + "fixedbitset_0.5.7": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "flate2_1.1.8": "{\"dependencies\":[{\"name\":\"cloudflare-zlib-sys\",\"optional\":true,\"req\":\"^0.3.6\"},{\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"libz-ng-sys\",\"optional\":true,\"req\":\"^1.1.16\"},{\"default_features\":false,\"name\":\"libz-sys\",\"optional\":true,\"req\":\"^1.1.20\"},{\"default_features\":false,\"features\":[\"with-alloc\",\"simd\"],\"name\":\"miniz_oxide\",\"req\":\"^0.8.5\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"emscripten\\\")))\"},{\"default_features\":false,\"features\":[\"with-alloc\",\"simd\"],\"name\":\"miniz_oxide\",\"optional\":true,\"req\":\"^0.8.5\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"std\",\"rust-allocator\"],\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.5.5\"}],\"features\":{\"any_c_zlib\":[\"any_zlib\"],\"any_impl\":[],\"any_zlib\":[\"any_impl\"],\"cloudflare_zlib\":[\"any_c_zlib\",\"cloudflare-zlib-sys\"],\"default\":[\"rust_backend\"],\"miniz-sys\":[\"rust_backend\"],\"rust_backend\":[\"miniz_oxide\",\"any_impl\"],\"zlib\":[\"any_c_zlib\",\"libz-sys\"],\"zlib-default\":[\"any_c_zlib\",\"libz-sys/default\"],\"zlib-ng\":[\"any_c_zlib\",\"libz-ng-sys\"],\"zlib-ng-compat\":[\"zlib\",\"libz-sys/zlib-ng\"],\"zlib-rs\":[\"any_zlib\",\"dep:zlib-rs\"]}}", "float-cmp_0.10.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.1\"}],\"features\":{\"default\":[\"ratio\"],\"ratio\":[\"num-traits\"],\"std\":[]}}", + "fluent-bundle_0.15.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"fluent-langneg\",\"req\":\"^0.13\"},{\"name\":\"fluent-syntax\",\"req\":\"^0.11.1\"},{\"kind\":\"dev\",\"name\":\"iai\",\"req\":\"^0.1\"},{\"name\":\"intl-memoizer\",\"req\":\"^0.5.2\"},{\"name\":\"intl_pluralrules\",\"req\":\"^7.0.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"rustc-hash\",\"req\":\"^1\"},{\"name\":\"self_cell\",\"req\":\"^0.10\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.8\"},{\"name\":\"smallvec\",\"req\":\"^1\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{\"all-benchmarks\":[],\"default\":[]}}", + "fluent-langneg_0.13.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"unic-langid\",\"req\":\"^0.9\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"unic-locale\",\"req\":\"^0.9\"}],\"features\":{\"cldr\":[\"unic-langid/likelysubtags\"],\"default\":[]}}", + "fluent-syntax_0.11.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"iai\",\"req\":\"^0.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"}],\"features\":{\"all-benchmarks\":[],\"default\":[],\"json\":[\"serde\",\"serde_json\"]}}", + "fluent_0.16.1": "{\"dependencies\":[{\"name\":\"fluent-bundle\",\"req\":\"^0.15.3\"},{\"name\":\"fluent-pseudo\",\"optional\":true,\"req\":\"^0.3.2\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", "flume_0.11.1": "{\"dependencies\":[{\"features\":[\"attributes\",\"unstable\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.13.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.5\"},{\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"^0.8.10\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.15\"},{\"features\":[\"getrandom\"],\"name\":\"nanorand\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"features\":[\"mutex\"],\"name\":\"spin1\",\"package\":\"spin\",\"req\":\"^0.9.8\"},{\"features\":[\"rt\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.16.1\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1.1.0\"}],\"features\":{\"async\":[\"futures-sink\",\"futures-core\"],\"default\":[\"async\",\"select\",\"eventual-fairness\"],\"eventual-fairness\":[\"select\",\"nanorand\"],\"select\":[],\"spin\":[]}}", "flume_0.12.0": "{\"dependencies\":[{\"features\":[\"attributes\",\"unstable\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.13.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.5\"},{\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"^0.8.10\"},{\"features\":[\"std\",\"js\"],\"name\":\"fastrand\",\"optional\":true,\"req\":\"^2.3\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.15\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"features\":[\"mutex\"],\"name\":\"spin1\",\"package\":\"spin\",\"req\":\"^0.9.8\"},{\"features\":[\"rt\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.16.1\"},{\"kind\":\"dev\",\"name\":\"waker-fn\",\"req\":\"^1.1.0\"}],\"features\":{\"async\":[\"futures-sink\",\"futures-core\"],\"default\":[\"async\",\"select\",\"eventual-fairness\"],\"eventual-fairness\":[\"select\",\"fastrand\"],\"select\":[],\"spin\":[]}}", "fnv_1.0.7": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", @@ -766,7 +787,7 @@ "foreign-types-shared_0.3.1": "{\"dependencies\":[],\"features\":{}}", "foreign-types_0.3.2": "{\"dependencies\":[{\"name\":\"foreign-types-shared\",\"req\":\"^0.1\"}],\"features\":{}}", "foreign-types_0.5.0": "{\"dependencies\":[{\"name\":\"foreign-types-macros\",\"req\":\"^0.2\"},{\"name\":\"foreign-types-shared\",\"req\":\"^0.3\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"foreign-types-macros/std\"]}}", - "form_urlencoded_1.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"percent-encoding\",\"req\":\"^2.3.0\"}],\"features\":{\"alloc\":[\"percent-encoding/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"percent-encoding/std\"]}}", + "form_urlencoded_1.2.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"percent-encoding\",\"req\":\"^2.3.0\"}],\"features\":{\"alloc\":[\"percent-encoding/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"percent-encoding/std\"]}}", "fs_extra_1.3.0": "{\"dependencies\":[],\"features\":{}}", "fsevent-sys_4.1.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.68\"}],\"features\":{}}", "fslock_0.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.66\",\"target\":\"cfg(unix)\"},{\"features\":[\"minwindef\",\"minwinbase\",\"winbase\",\"errhandlingapi\",\"winerror\",\"winnt\",\"synchapi\",\"handleapi\",\"fileapi\",\"processthreadsapi\"],\"name\":\"winapi\",\"req\":\"^0.3.8\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", @@ -784,11 +805,11 @@ "fxhash_0.2.1": "{\"dependencies\":[{\"name\":\"byteorder\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"seahash\",\"req\":\"^3.0.5\"}],\"features\":{}}", "generator_0.8.8": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"libc\",\"req\":\"^0.2.100\",\"target\":\"cfg(unix)\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"name\":\"windows-link\",\"req\":\">=0.1, <=0.2\",\"target\":\"cfg(windows)\"},{\"name\":\"windows-result\",\"req\":\">=0.3.1, <=0.4\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "generic-array_0.14.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"typenum\",\"req\":\"^1.12\"},{\"kind\":\"build\",\"name\":\"version_check\",\"req\":\"^0.9\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"more_lengths\":[]}}", - "gethostname_0.4.3": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.141\",\"target\":\"cfg(not(windows))\"},{\"name\":\"windows-targets\",\"req\":\"^0.48\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "getopts_0.2.23": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"},{\"name\":\"unicode-width\",\"req\":\"^0.2.0\"}],\"features\":{\"rustc-dep-of-std\":[\"unicode-width/rustc-dep-of-std\",\"std\",\"core\"]}}", - "getrandom_0.2.16": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"name\":\"wasi\",\"req\":\"^0.11\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.18\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"}],\"features\":{\"custom\":[],\"js\":[\"wasm-bindgen\",\"js-sys\"],\"linux_disable_fallback\":[],\"rdrand\":[],\"rustc-dep-of-std\":[\"compiler_builtins\",\"core\",\"libc/rustc-dep-of-std\",\"wasi/rustc-dep-of-std\"],\"std\":[],\"test-in-browser\":[]}}", - "getrandom_0.3.3": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.77\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"netbsd\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"solaris\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"vxworks\\\")\"},{\"default_features\":false,\"name\":\"r-efi\",\"req\":\"^5.1\",\"target\":\"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\"},{\"default_features\":false,\"name\":\"wasi\",\"req\":\"^0.14\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.98\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"}],\"features\":{\"rustc-dep-of-std\":[\"dep:compiler_builtins\",\"dep:core\"],\"std\":[],\"wasm_js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"]}}", - "gimli_0.31.1": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"test-assembler\",\"req\":\"^0.1.3\"}],\"features\":{\"default\":[\"read-all\",\"write\"],\"endian-reader\":[\"read\",\"dep:stable_deref_trait\"],\"fallible-iterator\":[\"dep:fallible-iterator\"],\"read\":[\"read-core\"],\"read-all\":[\"read\",\"std\",\"fallible-iterator\",\"endian-reader\"],\"read-core\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\",\"dep:compiler_builtins\"],\"std\":[\"fallible-iterator?/std\",\"stable_deref_trait?/std\"],\"write\":[\"dep:indexmap\"]}}", + "gethostname_1.1.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"system\"],\"name\":\"rustix\",\"req\":\"^1.0.3\",\"target\":\"cfg(not(windows))\"},{\"name\":\"windows-link\",\"req\":\"^0.2.1\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "getopts_0.2.24": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.0\"}],\"features\":{\"default\":[\"unicode\"],\"rustc-dep-of-std\":[\"std\",\"core\"],\"unicode\":[\"dep:unicode-width\"]}}", + "getrandom_0.2.17": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"name\":\"wasi\",\"req\":\"^0.11\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.18\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"}],\"features\":{\"custom\":[],\"js\":[\"wasm-bindgen\",\"js-sys\"],\"linux_disable_fallback\":[],\"rdrand\":[],\"rustc-dep-of-std\":[\"compiler_builtins\",\"core\",\"libc/rustc-dep-of-std\",\"wasi/rustc-dep-of-std\"],\"std\":[],\"test-in-browser\":[]}}", + "getrandom_0.3.4": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.77\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), not(any(all(target_os = \\\"linux\\\", target_env = \\\"\\\"), getrandom_backend = \\\"custom\\\", getrandom_backend = \\\"linux_raw\\\", getrandom_backend = \\\"rdrand\\\", getrandom_backend = \\\"rndr\\\"))))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"hurd\\\", target_os = \\\"illumos\\\", target_os = \\\"cygwin\\\", all(target_os = \\\"horizon\\\", target_arch = \\\"arm\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"haiku\\\", target_os = \\\"redox\\\", target_os = \\\"nto\\\", target_os = \\\"aix\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\", target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"openbsd\\\", target_os = \\\"vita\\\", target_os = \\\"emscripten\\\"))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"netbsd\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"solaris\\\")\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.154\",\"target\":\"cfg(target_os = \\\"vxworks\\\")\"},{\"default_features\":false,\"name\":\"r-efi\",\"req\":\"^5.1\",\"target\":\"cfg(all(target_os = \\\"uefi\\\", getrandom_backend = \\\"efi_rng\\\"))\"},{\"default_features\":false,\"name\":\"wasip2\",\"req\":\"^1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\", target_env = \\\"p2\\\"))\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.98\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"}],\"features\":{\"std\":[],\"wasm_js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"]}}", + "gimli_0.32.3": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"test-assembler\",\"req\":\"^0.1.3\"}],\"features\":{\"default\":[\"read-all\",\"write\"],\"endian-reader\":[\"read\",\"dep:stable_deref_trait\"],\"fallible-iterator\":[\"dep:fallible-iterator\"],\"read\":[\"read-core\"],\"read-all\":[\"read\",\"std\",\"fallible-iterator\",\"endian-reader\"],\"read-core\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\"],\"std\":[\"fallible-iterator?/std\",\"stable_deref_trait?/std\"],\"write\":[\"dep:indexmap\"]}}", "git+https://github.com/JakkuSakura/tokio-tungstenite?rev=2ae536b0de793f3ddf31fc2f22d445bf1ef2023d#2ae536b0de793f3ddf31fc2f22d445bf1ef2023d_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"handshake\"],\"default\":[\"connect\",\"handshake\"],\"handshake\":[\"tungstenite/handshake\"],\"native-tls\":[\"native-tls-crate\",\"tokio-native-tls\",\"stream\",\"tungstenite/native-tls\",\"handshake\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\",\"tungstenite/native-tls-vendored\"],\"proxy\":[\"tungstenite/proxy\",\"tokio/net\",\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"stream\":[],\"url\":[\"tungstenite/url\"]},\"strip_prefix\":\"\"}", "git+https://github.com/JakkuSakura/tungstenite-rs?rev=f514de8644821113e5d18a027d6d28a5c8cc0a6e#f514de8644821113e5d18a027d6d28a5c8cc0a6e_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"name\":\"rand\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true},{\"name\":\"thiserror\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true},{\"name\":\"utf-8\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"handshake\":[\"data-encoding\",\"http\",\"httparse\",\"sha1\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", "git+https://github.com/dzbarsky/rules_rust?rev=b56cbaa8465e74127f1ea216f813cd377295ad81#b56cbaa8465e74127f1ea216f813cd377295ad81_runfiles": "{\"dependencies\":[],\"features\":{},\"strip_prefix\":\"\"}", @@ -798,12 +819,12 @@ "git+https://github.com/nornagon/ratatui?branch=nornagon-v0.29.0-patch#9b2ad1298408c45918ee9f8241a6f95498cdbed2_ratatui": "{\"dependencies\":[{\"name\":\"bitflags\"},{\"name\":\"cassowary\"},{\"name\":\"compact_str\"},{\"default_features\":true,\"features\":[],\"name\":\"crossterm\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"document-features\",\"optional\":true},{\"name\":\"indoc\"},{\"name\":\"instability\"},{\"name\":\"itertools\"},{\"name\":\"lru\"},{\"default_features\":true,\"features\":[],\"name\":\"palette\",\"optional\":true},{\"name\":\"paste\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"strum\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"termwiz\",\"optional\":true},{\"default_features\":true,\"features\":[\"local-offset\"],\"name\":\"time\",\"optional\":true},{\"name\":\"unicode-segmentation\"},{\"name\":\"unicode-truncate\"},{\"name\":\"unicode-width\"},{\"default_features\":true,\"features\":[],\"name\":\"termion\",\"optional\":true,\"target\":\"cfg(not(windows))\"}],\"features\":{\"all-widgets\":[\"widget-calendar\"],\"crossterm\":[\"dep:crossterm\"],\"default\":[\"crossterm\",\"underline-color\"],\"macros\":[],\"palette\":[\"dep:palette\"],\"scrolling-regions\":[],\"serde\":[\"dep:serde\",\"bitflags/serde\",\"compact_str/serde\"],\"termion\":[\"dep:termion\"],\"termwiz\":[\"dep:termwiz\"],\"underline-color\":[\"dep:crossterm\"],\"unstable\":[\"unstable-rendered-line-info\",\"unstable-widget-ref\",\"unstable-backend-writer\"],\"unstable-backend-writer\":[],\"unstable-rendered-line-info\":[],\"unstable-widget-ref\":[],\"widget-calendar\":[\"dep:time\"]},\"strip_prefix\":\"\"}", "glob_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3\"}],\"features\":{}}", "globset_0.4.18": "{\"dependencies\":[{\"name\":\"aho-corasick\",\"req\":\"^1.1.1\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex-syntax\",\"req\":\"^0.8.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.188\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.107\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"log\"],\"serde1\":[\"serde\"],\"simd-accel\":[]}}", - "h2_0.4.11": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^0.26\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", - "half_2.6.0": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.4.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"crunchy\",\"req\":\"^0.2.2\",\"target\":\"cfg(target_arch = \\\"spirv\\\")\"},{\"kind\":\"dev\",\"name\":\"crunchy\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.16\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"rand_distr\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerocopy\",\"optional\":true,\"req\":\"^0.8.23\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"rand_distr\":[\"dep:rand\",\"dep:rand_distr\"],\"std\":[\"alloc\"],\"use-intrinsics\":[]}}", + "h2_0.4.13": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", + "half_2.7.1": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.4.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"crunchy\",\"req\":\"^0.2.2\",\"target\":\"cfg(target_arch = \\\"spirv\\\")\"},{\"kind\":\"dev\",\"name\":\"crunchy\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.16\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"rand_distr\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"derive\",\"simd\"],\"name\":\"zerocopy\",\"req\":\"^0.8.26\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"nightly\":[],\"rand_distr\":[\"dep:rand\",\"dep:rand_distr\"],\"std\":[\"alloc\"],\"use-intrinsics\":[],\"zerocopy\":[]}}", "hashbrown_0.12.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"bumpalo\",\"optional\":true,\"req\":\"^3.5.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"ahash-compile-time-rng\":[\"ahash/compile-time-rng\"],\"default\":[\"ahash\",\"inline-more\"],\"inline-more\":[],\"nightly\":[],\"raw\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"compiler_builtins\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", "hashbrown_0.14.5": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.8.7\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.7.42\"},{\"features\":[\"validation\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7.42\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"ahash\",\"inline-more\",\"allocator-api2\"],\"inline-more\":[],\"nightly\":[\"allocator-api2?/nightly\",\"bumpalo/allocator_api\"],\"raw\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"compiler_builtins\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", - "hashbrown_0.15.4": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.1.2\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"default-hasher\",\"inline-more\",\"allocator-api2\",\"equivalent\",\"raw-entry\"],\"default-hasher\":[\"dep:foldhash\"],\"inline-more\":[],\"nightly\":[\"bumpalo/allocator_api\"],\"raw-entry\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", - "hashbrown_0.16.0": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"default-hasher\",\"inline-more\",\"allocator-api2\",\"equivalent\",\"raw-entry\"],\"default-hasher\":[\"dep:foldhash\"],\"inline-more\":[],\"nightly\":[\"foldhash?/nightly\",\"bumpalo/allocator_api\"],\"raw-entry\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", + "hashbrown_0.15.5": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.1.2\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"default-hasher\",\"inline-more\",\"allocator-api2\",\"equivalent\",\"raw-entry\"],\"default-hasher\":[\"dep:foldhash\"],\"inline-more\":[],\"nightly\":[\"bumpalo/allocator_api\"],\"raw-entry\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", + "hashbrown_0.16.1": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"allocator-api2\",\"optional\":true,\"req\":\"^0.2.9\"},{\"features\":[\"allocator-api2\"],\"kind\":\"dev\",\"name\":\"bumpalo\",\"req\":\"^3.13.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.155\",\"target\":\"cfg(unix)\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.9.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"default-hasher\",\"inline-more\",\"allocator-api2\",\"equivalent\",\"raw-entry\"],\"default-hasher\":[\"dep:foldhash\"],\"inline-more\":[],\"nightly\":[\"foldhash?/nightly\",\"bumpalo/allocator_api\"],\"raw-entry\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[],\"serde\":[\"dep:serde_core\",\"dep:serde\"]}}", "hashlink_0.10.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"serde_impl\":[\"serde\"]}}", "heck_0.5.0": "{\"dependencies\":[],\"features\":{}}", "hermit-abi_0.5.2": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[],\"rustc-dep-of-std\":[\"core\",\"alloc\"]}}", @@ -812,67 +833,74 @@ "hickory-resolver_0.25.2": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.50\"},{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"futures-executor\",\"req\":\"^0.3.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-util\",\"req\":\"^0.3.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"hickory-proto\",\"req\":\"^0.25\"},{\"name\":\"ipconfig\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(windows)\"},{\"features\":[\"sync\"],\"name\":\"moka\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"critical-section\"],\"name\":\"once_cell\",\"req\":\"^1.20.0\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"log\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.11.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"rand\",\"req\":\"^0.9\"},{\"features\":[\"system\"],\"name\":\"resolv-conf\",\"optional\":true,\"req\":\"^0.7.0\"},{\"default_features\":false,\"features\":[\"logging\",\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.23\"},{\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"name\":\"smallvec\",\"req\":\"^1.6\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2\"},{\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.21\"},{\"features\":[\"macros\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.21\"},{\"default_features\":false,\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.8.14\"},{\"default_features\":false,\"name\":\"tracing\",\"req\":\"^0.1.30\"},{\"default_features\":false,\"features\":[\"env-filter\",\"fmt\",\"std\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^0.26\"}],\"features\":{\"__dnssec\":[],\"__h3\":[\"__quic\"],\"__https\":[\"__tls\"],\"__quic\":[\"dep:quinn\",\"__tls\"],\"__tls\":[\"dep:rustls\",\"dep:tokio-rustls\",\"tokio\"],\"backtrace\":[\"dep:backtrace\",\"hickory-proto/backtrace\"],\"default\":[\"system-config\",\"tokio\"],\"dnssec-aws-lc-rs\":[\"hickory-proto/dnssec-aws-lc-rs\",\"__dnssec\"],\"dnssec-ring\":[\"hickory-proto/dnssec-ring\",\"__dnssec\"],\"h3-aws-lc-rs\":[\"hickory-proto/h3-aws-lc-rs\",\"__h3\"],\"h3-ring\":[\"hickory-proto/h3-ring\",\"__h3\"],\"https-aws-lc-rs\":[\"hickory-proto/https-aws-lc-rs\",\"__https\"],\"https-ring\":[\"hickory-proto/https-ring\",\"__https\"],\"quic-aws-lc-rs\":[\"hickory-proto/quic-aws-lc-rs\",\"__quic\",\"quinn/rustls-aws-lc-rs\"],\"quic-ring\":[\"hickory-proto/quic-ring\",\"__quic\",\"quinn/rustls-ring\"],\"rustls-platform-verifier\":[\"hickory-proto/rustls-platform-verifier\"],\"serde\":[\"dep:serde\",\"hickory-proto/serde\"],\"system-config\":[\"dep:ipconfig\",\"dep:resolv-conf\"],\"tls-aws-lc-rs\":[\"hickory-proto/tls-aws-lc-rs\",\"__tls\"],\"tls-ring\":[\"hickory-proto/tls-ring\",\"__tls\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"hickory-proto/tokio\"],\"webpki-roots\":[\"dep:webpki-roots\",\"hickory-proto/webpki-roots\"]}}", "hkdf_0.12.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"blobby\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"hmac\",\"req\":\"^0.12.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"std\":[\"hmac/std\"]}}", "hmac_0.12.1": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.3\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"md-5\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha-1\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"reset\":[],\"std\":[\"digest/std\"]}}", - "home_0.5.11": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "hostname_0.4.1": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(unix, target_os = \\\"redox\\\"))\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.6.1\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"windows-bindgen\",\"req\":\"^0.61\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"windows-link\",\"req\":\"^0.1.1\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"default\":[],\"set\":[]}}", + "home_0.5.12": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "hostname_0.4.2": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(unix, target_os = \\\"redox\\\"))\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.6.1\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"windows-bindgen\",\"req\":\"^0.65\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"default\":[],\"set\":[]}}", "http-body-util_0.1.3": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"rt\",\"sync\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"}],\"features\":{\"channel\":[\"dep:tokio\"],\"default\":[],\"full\":[\"channel\"]}}", "http-body_1.0.1": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^1\"}],\"features\":{}}", "http-range-header_0.4.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.8.3\"}],\"features\":{}}", "http_0.2.12": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"indexmap\",\"req\":\"<=1.8\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"seahash\",\"req\":\"^3.0.5\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{}}", - "http_1.3.1": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "http_1.4.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "httparse_1.10.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.5\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "httpdate_1.0.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"}],\"features\":{}}", "hyper-rustls_0.27.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"hyper\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"server-auto\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.4\"},{\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"rustls\",\"req\":\"^0.23\"},{\"default_features\":false,\"features\":[\"tls12\"],\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rustls-pemfile\",\"req\":\"^2\"},{\"name\":\"rustls-platform-verifier\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"tokio\",\"req\":\"^1.0\"},{\"features\":[\"io-std\",\"macros\",\"net\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"aws-lc-rs\":[\"rustls/aws_lc_rs\"],\"default\":[\"native-tokio\",\"http1\",\"tls12\",\"logging\",\"aws-lc-rs\"],\"fips\":[\"aws-lc-rs\",\"rustls/fips\"],\"http1\":[\"hyper-util/http1\"],\"http2\":[\"hyper-util/http2\"],\"logging\":[\"log\",\"tokio-rustls/logging\",\"rustls/logging\"],\"native-tokio\":[\"rustls-native-certs\"],\"ring\":[\"rustls/ring\"],\"tls12\":[\"tokio-rustls/tls12\",\"rustls/tls12\"],\"webpki-tokio\":[\"webpki-roots\"]}}", "hyper-timeout_0.5.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"hyper\",\"req\":\"^1.1\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"hyper-tls\",\"req\":\"^0.6\"},{\"features\":[\"client-legacy\",\"http1\"],\"name\":\"hyper-util\",\"req\":\"^0.1.10\"},{\"features\":[\"client-legacy\",\"http1\",\"server\",\"server-graceful\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.10\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"name\":\"tokio\",\"req\":\"^1.35\"},{\"features\":[\"io-std\",\"io-util\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.35\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{}}", "hyper-tls_0.6.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"name\":\"native-tls\",\"req\":\"^0.2.1\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"io-std\",\"macros\",\"io-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"},{\"name\":\"tokio-native-tls\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{\"alpn\":[\"native-tls/alpn\"],\"vendored\":[\"native-tls/vendored\"]}}", - "hyper-util_0.1.16": "{\"dependencies\":[{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1.7.1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.16\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.16\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1.6.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.4.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.35.0\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.5.9, <0.7\"},{\"name\":\"system-configuration\",\"optional\":true,\"req\":\"^0.6.1\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"test-util\",\"signal\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"windows-registry\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"client\":[\"hyper/client\",\"tokio/net\",\"dep:tracing\",\"dep:futures-channel\",\"dep:tower-service\"],\"client-legacy\":[\"client\",\"dep:socket2\",\"tokio/sync\",\"dep:libc\",\"dep:futures-util\"],\"client-proxy\":[\"client\",\"dep:base64\",\"dep:ipnet\",\"dep:percent-encoding\"],\"client-proxy-system\":[\"dep:system-configuration\",\"dep:windows-registry\"],\"default\":[],\"full\":[\"client\",\"client-legacy\",\"client-proxy\",\"client-proxy-system\",\"server\",\"server-auto\",\"server-graceful\",\"service\",\"http1\",\"http2\",\"tokio\",\"tracing\"],\"http1\":[\"hyper/http1\"],\"http2\":[\"hyper/http2\"],\"server\":[\"hyper/server\"],\"server-auto\":[\"server\",\"http1\",\"http2\"],\"server-graceful\":[\"server\",\"tokio/sync\"],\"service\":[\"dep:tower-service\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"tokio/time\"],\"tracing\":[\"dep:tracing\"]}}", - "hyper_1.7.0": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"optional\":true,\"req\":\"^1.1.2\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"form_urlencoded\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"sink\"],\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"httparse\",\"optional\":true,\"req\":\"^1.9\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"name\":\"pin-utils\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"spmc\",\"req\":\"^0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\",\"macros\",\"net\",\"io-std\",\"io-util\",\"rt\",\"rt-multi-thread\",\"sync\",\"time\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7.10\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"want\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"capi\":[],\"client\":[\"dep:want\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"default\":[],\"ffi\":[\"dep:http-body-util\",\"dep:futures-util\"],\"full\":[\"client\",\"http1\",\"http2\",\"server\"],\"http1\":[\"dep:atomic-waker\",\"dep:futures-channel\",\"dep:futures-core\",\"dep:httparse\",\"dep:itoa\",\"dep:pin-utils\"],\"http2\":[\"dep:futures-channel\",\"dep:futures-core\",\"dep:h2\"],\"nightly\":[],\"server\":[\"dep:httpdate\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"tracing\":[\"dep:tracing\"]}}", + "hyper-util_0.1.19": "{\"dependencies\":[{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1.7.1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.16\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.16\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1.8.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.4.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.35.0\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.5.9, <0.7\"},{\"name\":\"system-configuration\",\"optional\":true,\"req\":\">=0.5, <0.7\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"test-util\",\"signal\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tower-layer\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tower-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"windows-registry\",\"optional\":true,\"req\":\">=0.3, <0.7\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"client\":[\"hyper/client\",\"tokio/net\",\"dep:tracing\",\"dep:futures-channel\",\"dep:tower-service\"],\"client-legacy\":[\"client\",\"dep:socket2\",\"tokio/sync\",\"dep:libc\",\"dep:futures-util\"],\"client-pool\":[\"client\",\"dep:futures-util\",\"dep:tower-layer\"],\"client-proxy\":[\"client\",\"dep:base64\",\"dep:ipnet\",\"dep:percent-encoding\"],\"client-proxy-system\":[\"dep:system-configuration\",\"dep:windows-registry\"],\"default\":[],\"full\":[\"client\",\"client-legacy\",\"client-pool\",\"client-proxy\",\"client-proxy-system\",\"server\",\"server-auto\",\"server-graceful\",\"service\",\"http1\",\"http2\",\"tokio\",\"tracing\"],\"http1\":[\"hyper/http1\"],\"http2\":[\"hyper/http2\"],\"server\":[\"hyper/server\"],\"server-auto\":[\"server\",\"http1\",\"http2\"],\"server-graceful\":[\"server\",\"tokio/sync\"],\"service\":[\"dep:tower-service\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"tokio/time\"],\"tracing\":[\"dep:tracing\"]}}", + "hyper_1.8.1": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"optional\":true,\"req\":\"^1.1.2\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"form_urlencoded\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"sink\"],\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"httparse\",\"optional\":true,\"req\":\"^1.9\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"name\":\"pin-utils\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"spmc\",\"req\":\"^0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\",\"macros\",\"net\",\"io-std\",\"io-util\",\"rt\",\"rt-multi-thread\",\"sync\",\"time\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7.10\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"want\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"capi\":[],\"client\":[\"dep:want\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"default\":[],\"ffi\":[\"dep:http-body-util\",\"dep:futures-util\"],\"full\":[\"client\",\"http1\",\"http2\",\"server\"],\"http1\":[\"dep:atomic-waker\",\"dep:futures-channel\",\"dep:futures-core\",\"dep:httparse\",\"dep:itoa\",\"dep:pin-utils\"],\"http2\":[\"dep:futures-channel\",\"dep:futures-core\",\"dep:h2\"],\"nightly\":[],\"server\":[\"dep:httpdate\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"tracing\":[\"dep:tracing\"]}}", + "i18n-config_0.4.8": "{\"dependencies\":[{\"name\":\"basic-toml\",\"req\":\"^0.1\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"serde\"],\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", + "i18n-embed-fl_0.9.4": "{\"dependencies\":[{\"name\":\"dashmap\",\"optional\":true,\"req\":\"^6.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"find-crate\",\"req\":\"^0.6\"},{\"name\":\"fluent\",\"req\":\"^0.16\"},{\"name\":\"fluent-syntax\",\"req\":\"^0.11\"},{\"name\":\"i18n-config\",\"req\":\"^0.4.7\"},{\"features\":[\"fluent-system\",\"filesystem-assets\"],\"name\":\"i18n-embed\",\"req\":\"^0.15.4\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"name\":\"proc-macro-error2\",\"req\":\"^2.0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rust-embed\",\"req\":\"^8.0\"},{\"name\":\"strsim\",\"req\":\"^0.11\"},{\"features\":[\"derive\",\"proc-macro\",\"parsing\",\"printing\",\"extra-traits\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{\"dashmap\":[\"dep:dashmap\"]}}", + "i18n-embed-impl_0.8.4": "{\"dependencies\":[{\"name\":\"find-crate\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"i18n-config\",\"optional\":true,\"req\":\"^0.4.7\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rust-embed\",\"req\":\"^8.0\"},{\"features\":[\"derive\",\"proc-macro\",\"parsing\",\"printing\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{\"default\":[],\"fluent-system\":[\"i18n-config\",\"find-crate\",\"quote\"],\"gettext-system\":[\"i18n-config\",\"find-crate\",\"quote\"]}}", + "i18n-embed_0.15.4": "{\"dependencies\":[{\"name\":\"arc-swap\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"fluent\",\"optional\":true,\"req\":\"^0.16\"},{\"name\":\"fluent-langneg\",\"req\":\"^0.13\"},{\"name\":\"fluent-syntax\",\"optional\":true,\"req\":\"^0.11\"},{\"name\":\"gettext\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"i18n-embed-impl\",\"optional\":true,\"req\":\"^0.8.4\"},{\"name\":\"intl-memoizer\",\"req\":\"^0.5\"},{\"name\":\"locale_config\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"maplit\",\"req\":\"^1.0\"},{\"name\":\"notify\",\"optional\":true,\"req\":\"^8.0.0\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"name\":\"rust-embed\",\"optional\":true,\"req\":\"^8.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.0\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tr\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.4\"},{\"features\":[\"Window\",\"Navigator\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"autoreload\":[\"notify\"],\"default\":[\"rust-embed\"],\"desktop-requester\":[\"locale_config\"],\"filesystem-assets\":[\"walkdir\"],\"fluent-system\":[\"fluent\",\"fluent-syntax\",\"parking_lot\",\"i18n-embed-impl\",\"i18n-embed-impl/fluent-system\",\"arc-swap\"],\"gettext-system\":[\"tr\",\"tr/gettext\",\"dep:gettext\",\"parking_lot\",\"i18n-embed-impl\",\"i18n-embed-impl/gettext-system\"],\"web-sys-requester\":[\"web-sys\"]}}", "iana-time-zone-haiku_0.1.2": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.79\"}],\"features\":{}}", - "iana-time-zone_0.1.63": "{\"dependencies\":[{\"name\":\"android_system_properties\",\"req\":\"^0.1.5\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.1\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.1\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"iana-time-zone-haiku\",\"req\":\"^0.1.1\",\"target\":\"cfg(target_os = \\\"haiku\\\")\"},{\"name\":\"js-sys\",\"req\":\"^0.3.66\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"log\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.46\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"windows-core\",\"req\":\">=0.56, <=0.61\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"fallback\":[]}}", + "iana-time-zone_0.1.65": "{\"dependencies\":[{\"name\":\"android_system_properties\",\"req\":\"^0.1.5\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.1\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.1\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"iana-time-zone-haiku\",\"req\":\"^0.1.1\",\"target\":\"cfg(target_os = \\\"haiku\\\")\"},{\"name\":\"js-sys\",\"req\":\"^0.3.66\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"log\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.46\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"windows-core\",\"req\":\">=0.56, <=0.62\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"fallback\":[]}}", "icu_collections_2.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"kind\":\"dev\",\"name\":\"iai\",\"req\":\"^0.1.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"zerovec\"],\"name\":\"potential_utf\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"parse\"],\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"features\":[\"derive\",\"yoke\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"serde?/alloc\",\"zerovec/alloc\"],\"databake\":[\"dep:databake\",\"zerovec/databake\"],\"serde\":[\"dep:serde\",\"zerovec/serde\",\"potential_utf/serde\",\"alloc\"]}}", "icu_decimal_2.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"fixed_decimal\",\"req\":\"^0.7.0\"},{\"features\":[\"wasm_js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"icu_decimal_data\",\"optional\":true,\"req\":\"~2.1.1\"},{\"default_features\":false,\"name\":\"icu_locale\",\"optional\":true,\"req\":\"~2.1.1\"},{\"default_features\":false,\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"icu_provider\",\"req\":\"^2.1.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_distr\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"writeable\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"serde?/alloc\",\"zerovec/alloc\"],\"compiled_data\":[\"dep:icu_decimal_data\",\"dep:icu_locale\",\"icu_locale?/compiled_data\",\"icu_provider/baked\"],\"datagen\":[\"serde\",\"dep:databake\",\"zerovec/databake\",\"icu_provider/export\",\"alloc\"],\"default\":[\"compiled_data\"],\"ryu\":[\"fixed_decimal/ryu\"],\"serde\":[\"dep:serde\",\"icu_provider/serde\",\"zerovec/serde\"]}}", "icu_decimal_data_2.1.1": "{\"dependencies\":[],\"features\":{}}", "icu_locale_2.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"icu_collections\",\"req\":\"~2.1.1\"},{\"default_features\":false,\"features\":[\"alloc\",\"zerovec\"],\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"icu_locale_data\",\"optional\":true,\"req\":\"~2.1.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"icu_provider\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"features\":[\"alloc\",\"zerovec\"],\"name\":\"potential_utf\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"alloc\",\"zerovec\"],\"name\":\"tinystr\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\",\"yoke\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"compiled_data\":[\"dep:icu_locale_data\",\"icu_provider/baked\"],\"datagen\":[\"serde\",\"dep:databake\",\"zerovec/databake\",\"icu_locale_core/databake\",\"tinystr/databake\",\"icu_collections/databake\",\"icu_provider/export\"],\"default\":[\"compiled_data\"],\"serde\":[\"dep:serde\",\"icu_locale_core/serde\",\"tinystr/serde\",\"zerovec/serde\",\"icu_provider/serde\",\"potential_utf/serde\",\"icu_collections/serde\"]}}", "icu_locale_core_2.1.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"name\":\"litemap\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"tinystr\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"writeable\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"litemap/alloc\",\"tinystr/alloc\",\"writeable/alloc\",\"serde?/alloc\"],\"databake\":[\"dep:databake\",\"alloc\"],\"serde\":[\"dep:serde\",\"tinystr/serde\"],\"zerovec\":[\"dep:zerovec\",\"tinystr/zerovec\"]}}", - "icu_locale_data_2.1.1": "{\"dependencies\":[],\"features\":{}}", + "icu_locale_data_2.1.2": "{\"dependencies\":[],\"features\":{}}", "icu_normalizer_2.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"arraystring\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"arrayvec\",\"req\":\"^0.7.2\"},{\"kind\":\"dev\",\"name\":\"atoi\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"detone\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"icu_collections\",\"req\":\"~2.1.1\"},{\"default_features\":false,\"name\":\"icu_normalizer_data\",\"optional\":true,\"req\":\"~2.1.1\"},{\"default_features\":false,\"name\":\"icu_properties\",\"optional\":true,\"req\":\"~2.1.1\"},{\"default_features\":false,\"name\":\"icu_provider\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"features\":[\"derive\",\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"smallvec\",\"req\":\"^1.10.0\"},{\"default_features\":false,\"name\":\"utf16_iter\",\"optional\":true,\"req\":\"^1.0.2\"},{\"default_features\":false,\"name\":\"utf8_iter\",\"optional\":true,\"req\":\"^1.0.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"write16\",\"optional\":true,\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"arrayvec\",\"smallvec\"],\"kind\":\"dev\",\"name\":\"write16\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"compiled_data\":[\"dep:icu_normalizer_data\",\"icu_properties?/compiled_data\",\"icu_provider/baked\"],\"datagen\":[\"serde\",\"dep:databake\",\"icu_properties\",\"icu_collections/databake\",\"zerovec/databake\",\"icu_properties?/datagen\",\"icu_provider/export\"],\"default\":[\"compiled_data\",\"utf8_iter\",\"utf16_iter\"],\"experimental\":[],\"icu_properties\":[\"dep:icu_properties\"],\"serde\":[\"dep:serde\",\"icu_collections/serde\",\"zerovec/serde\",\"icu_properties?/serde\",\"icu_provider/serde\"],\"utf16_iter\":[\"dep:utf16_iter\",\"dep:write16\"],\"utf8_iter\":[\"dep:utf8_iter\"],\"write16\":[]}}", "icu_normalizer_data_2.1.1": "{\"dependencies\":[],\"features\":{}}", - "icu_properties_2.1.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"icu_collections\",\"req\":\"~2.1.1\"},{\"default_features\":false,\"features\":[\"zerovec\"],\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"icu_properties_data\",\"optional\":true,\"req\":\"~2.1.1\"},{\"default_features\":false,\"name\":\"icu_provider\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"unicode-bidi\",\"optional\":true,\"req\":\"^0.3.11\"},{\"default_features\":false,\"features\":[\"yoke\",\"zerofrom\"],\"name\":\"zerotrie\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\",\"yoke\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"zerovec/alloc\",\"icu_collections/alloc\",\"serde?/alloc\"],\"compiled_data\":[\"dep:icu_properties_data\",\"icu_provider/baked\"],\"datagen\":[\"serde\",\"dep:databake\",\"zerovec/databake\",\"icu_collections/databake\",\"icu_locale_core/databake\",\"zerotrie/databake\",\"icu_provider/export\"],\"default\":[\"compiled_data\"],\"serde\":[\"dep:serde\",\"icu_locale_core/serde\",\"zerovec/serde\",\"icu_collections/serde\",\"icu_provider/serde\",\"zerotrie/serde\"],\"unicode_bidi\":[\"dep:unicode-bidi\"]}}", - "icu_properties_data_2.1.1": "{\"dependencies\":[],\"features\":{}}", + "icu_properties_2.1.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"icu_collections\",\"req\":\"~2.1.1\"},{\"default_features\":false,\"features\":[\"zerovec\"],\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"icu_properties_data\",\"optional\":true,\"req\":\"~2.1.2\"},{\"default_features\":false,\"name\":\"icu_provider\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"unicode-bidi\",\"optional\":true,\"req\":\"^0.3.11\"},{\"default_features\":false,\"features\":[\"yoke\",\"zerofrom\"],\"name\":\"zerotrie\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\",\"yoke\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"zerovec/alloc\",\"icu_collections/alloc\",\"serde?/alloc\"],\"compiled_data\":[\"dep:icu_properties_data\",\"icu_provider/baked\"],\"datagen\":[\"serde\",\"dep:databake\",\"zerovec/databake\",\"icu_collections/databake\",\"icu_locale_core/databake\",\"zerotrie/databake\",\"icu_provider/export\"],\"default\":[\"compiled_data\"],\"serde\":[\"dep:serde\",\"icu_locale_core/serde\",\"zerovec/serde\",\"icu_collections/serde\",\"icu_provider/serde\",\"zerotrie/serde\"],\"unicode_bidi\":[\"dep:unicode-bidi\"]}}", + "icu_properties_data_2.1.2": "{\"dependencies\":[],\"features\":{}}", "icu_provider_2.1.1": "{\"dependencies\":[{\"name\":\"bincode\",\"optional\":true,\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"name\":\"erased-serde\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"icu_locale_core\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"default_features\":false,\"name\":\"postcard\",\"optional\":true,\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.45\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.2.0\"},{\"default_features\":false,\"name\":\"writeable\",\"optional\":true,\"req\":\"^0.6.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerotrie\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"zerovec\",\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"icu_locale_core/alloc\",\"serde?/alloc\",\"yoke/alloc\",\"zerofrom/alloc\",\"zerovec/alloc\",\"zerotrie?/alloc\",\"dep:stable_deref_trait\",\"dep:writeable\"],\"baked\":[\"dep:zerotrie\",\"dep:writeable\"],\"deserialize_bincode_1\":[\"serde\",\"dep:bincode\",\"std\"],\"deserialize_json\":[\"serde\",\"dep:serde_json\"],\"deserialize_postcard_1\":[\"serde\",\"dep:postcard\"],\"export\":[\"serde\",\"dep:erased-serde\",\"dep:databake\",\"std\",\"sync\",\"dep:postcard\",\"zerovec/databake\"],\"logging\":[\"dep:log\"],\"serde\":[\"dep:serde\",\"yoke/serde\"],\"std\":[\"alloc\"],\"sync\":[],\"zerotrie\":[]}}", "ident_case_1.0.1": "{\"dependencies\":[],\"features\":{}}", - "idna_1.0.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"name\":\"idna_adapter\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\"],\"name\":\"smallvec\",\"req\":\"^1.13.1\"},{\"kind\":\"dev\",\"name\":\"tester\",\"req\":\"^0.9\"},{\"name\":\"utf8_iter\",\"req\":\"^1.0.4\"}],\"features\":{\"alloc\":[],\"compiled_data\":[\"idna_adapter/compiled_data\"],\"default\":[\"std\",\"compiled_data\"],\"std\":[\"alloc\"]}}", + "idna_1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"name\":\"idna_adapter\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\"],\"name\":\"smallvec\",\"req\":\"^1.13.1\"},{\"kind\":\"dev\",\"name\":\"tester\",\"req\":\"^0.9\"},{\"name\":\"utf8_iter\",\"req\":\"^1.0.4\"}],\"features\":{\"alloc\":[],\"compiled_data\":[\"idna_adapter/compiled_data\"],\"default\":[\"std\",\"compiled_data\"],\"std\":[\"alloc\"]}}", "idna_adapter_1.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"icu_normalizer\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"icu_properties\",\"req\":\"^2\"}],\"features\":{\"compiled_data\":[\"icu_normalizer/compiled_data\",\"icu_properties/compiled_data\"]}}", - "ignore_0.4.23": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.8\"},{\"name\":\"crossbeam-deque\",\"req\":\"^0.8.3\"},{\"name\":\"globset\",\"req\":\"^0.4.15\"},{\"name\":\"log\",\"req\":\"^0.4.20\"},{\"name\":\"memchr\",\"req\":\"^2.6.3\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\",\"dfa-onepass\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"name\":\"same-file\",\"req\":\"^1.0.6\"},{\"name\":\"walkdir\",\"req\":\"^2.4.0\"},{\"name\":\"winapi-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"simd-accel\":[]}}", + "ignore_0.4.25": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-deque\",\"req\":\"^0.8.3\"},{\"name\":\"globset\",\"req\":\"^0.4.18\"},{\"name\":\"log\",\"req\":\"^0.4.20\"},{\"name\":\"memchr\",\"req\":\"^2.6.3\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\",\"dfa-onepass\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"name\":\"same-file\",\"req\":\"^1.0.6\"},{\"name\":\"walkdir\",\"req\":\"^2.4.0\"},{\"name\":\"winapi-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"simd-accel\":[]}}", "image_0.25.9": "{\"dependencies\":[{\"features\":[\"extern_crate_alloc\"],\"name\":\"bytemuck\",\"req\":\"^1.8.0\"},{\"name\":\"byteorder-lite\",\"req\":\"^0.1.0\"},{\"name\":\"color_quant\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\"},{\"name\":\"dav1d\",\"optional\":true,\"req\":\"^0.10.3\"},{\"default_features\":false,\"name\":\"exr\",\"optional\":true,\"req\":\"^1.74.0\"},{\"name\":\"gif\",\"optional\":true,\"req\":\"^0.14.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"name\":\"image-webp\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"moxcms\",\"req\":\"^0.7.4\"},{\"name\":\"mp4parse\",\"optional\":true,\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"num-complex\",\"req\":\"^0.4\"},{\"name\":\"num-traits\",\"req\":\"^0.2.0\"},{\"name\":\"png\",\"optional\":true,\"req\":\"^0.18.0\"},{\"name\":\"qoi\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"ravif\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7.0\"},{\"default_features\":false,\"name\":\"rgb\",\"optional\":true,\"req\":\"^0.8.48\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.214\"},{\"name\":\"tiff\",\"optional\":true,\"req\":\"^0.10.3\"},{\"default_features\":false,\"name\":\"zune-core\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"zune-jpeg\",\"optional\":true,\"req\":\"^0.5.5\"}],\"features\":{\"avif\":[\"dep:ravif\",\"dep:rgb\"],\"avif-native\":[\"dep:mp4parse\",\"dep:dav1d\"],\"benchmarks\":[],\"bmp\":[],\"color_quant\":[\"dep:color_quant\"],\"dds\":[],\"default\":[\"rayon\",\"default-formats\"],\"default-formats\":[\"avif\",\"bmp\",\"dds\",\"exr\",\"ff\",\"gif\",\"hdr\",\"ico\",\"jpeg\",\"png\",\"pnm\",\"qoi\",\"tga\",\"tiff\",\"webp\"],\"exr\":[\"dep:exr\"],\"ff\":[],\"gif\":[\"dep:gif\",\"dep:color_quant\"],\"hdr\":[],\"ico\":[\"bmp\",\"png\"],\"jpeg\":[\"dep:zune-core\",\"dep:zune-jpeg\"],\"nasm\":[\"ravif?/asm\"],\"png\":[\"dep:png\"],\"pnm\":[],\"qoi\":[\"dep:qoi\"],\"rayon\":[\"dep:rayon\",\"ravif?/threading\",\"exr?/rayon\"],\"serde\":[\"dep:serde\"],\"tga\":[],\"tiff\":[\"dep:tiff\"],\"webp\":[\"dep:image-webp\"]}}", "impl-more_0.1.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"eyre\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{}}", "include_dir_0.7.4": "{\"dependencies\":[{\"name\":\"glob\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"include_dir_macros\",\"req\":\"^0.7.4\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"default\":[],\"metadata\":[\"include_dir_macros/metadata\"],\"nightly\":[\"include_dir_macros/nightly\"]}}", "include_dir_macros_0.7.4": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"}],\"features\":{\"metadata\":[],\"nightly\":[]}}", - "indenter_0.3.3": "{\"dependencies\":[],\"features\":{\"default\":[],\"std\":[]}}", + "indenter_0.3.4": "{\"dependencies\":[],\"features\":{\"default\":[],\"std\":[]}}", "indexmap_1.9.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"raw\"],\"name\":\"hashbrown\",\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.3\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.4.1\"},{\"name\":\"rustc-rayon\",\"optional\":true,\"package\":\"rustc-rayon\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"}],\"features\":{\"serde-1\":[\"serde\"],\"std\":[],\"test_debug\":[],\"test_low_transition_point\":[]}}", - "indexmap_2.12.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"equivalent\",\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"hashbrown\",\"req\":\"^0.16\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.9\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"sval\",\"optional\":true,\"req\":\"^2\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde_core\",\"dep:serde\"],\"std\":[],\"test_debug\":[]}}", - "indoc_2.0.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"},{\"kind\":\"dev\",\"name\":\"unindent\",\"req\":\"^0.2.3\"}],\"features\":{}}", + "indexmap_2.13.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"equivalent\",\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"hashbrown\",\"req\":\"^0.16.1\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.9\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"sval\",\"optional\":true,\"req\":\"^2\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde_core\",\"dep:serde\"],\"std\":[],\"test_debug\":[]}}", + "indoc_2.0.7": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"},{\"kind\":\"dev\",\"name\":\"unindent\",\"req\":\"^0.2.3\"}],\"features\":{}}", "inotify-sys_0.1.5": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"}],\"features\":{}}", "inotify_0.11.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.1\"},{\"name\":\"inotify-sys\",\"req\":\"^0.1.3\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"maplit\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"},{\"features\":[\"net\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.0.1\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.1\"}],\"features\":{\"default\":[\"stream\"],\"stream\":[\"futures-core\",\"tokio\"]}}", "inout_0.1.4": "{\"dependencies\":[{\"name\":\"block-padding\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"generic-array\",\"req\":\"^0.14\"}],\"features\":{\"std\":[\"block-padding/std\"]}}", - "insta_1.46.0": "{\"dependencies\":[{\"features\":[\"derive\",\"env\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.1\"},{\"default_features\":false,\"name\":\"console\",\"optional\":true,\"req\":\"^0.15.4\"},{\"name\":\"csv\",\"optional\":true,\"req\":\"^1.1.6\"},{\"name\":\"globset\",\"optional\":true,\"req\":\">=0.4.6, <0.4.17\"},{\"name\":\"once_cell\",\"req\":\"^1.20.2\"},{\"name\":\"pest\",\"optional\":true,\"req\":\"^2.1.3\"},{\"name\":\"pest_derive\",\"optional\":true,\"req\":\"^2.1.0\"},{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.6.0\"},{\"name\":\"ron\",\"optional\":true,\"req\":\"^0.12.0\"},{\"kind\":\"dev\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.117\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.117\"},{\"features\":[\"inline\"],\"name\":\"similar\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.4.2\"},{\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"serde\",\"parse\",\"display\"],\"name\":\"toml_edit\",\"optional\":true,\"req\":\"^0.23.0\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.3.1\"}],\"features\":{\"_cargo_insta_internal\":[\"clap\"],\"colors\":[\"console\"],\"csv\":[\"dep:csv\",\"serde\"],\"default\":[\"colors\"],\"filters\":[\"regex\"],\"glob\":[\"walkdir\",\"globset\"],\"json\":[\"serde\"],\"redactions\":[\"pest\",\"pest_derive\",\"serde\"],\"ron\":[\"dep:ron\",\"serde\"],\"toml\":[\"dep:toml_edit\",\"dep:toml_writer\",\"serde\"],\"yaml\":[\"serde\"]}}", - "instability_0.3.9": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.20.10\"},{\"name\":\"indoc\",\"req\":\"^2.0.5\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.92\"},{\"name\":\"quote\",\"req\":\"^1.0.37\"},{\"features\":[\"derive\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0.90\"}],\"features\":{}}", - "inventory_0.3.20": "{\"dependencies\":[{\"name\":\"rustversion\",\"req\":\"^1.0\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\"}],\"features\":{}}", + "insta_1.46.2": "{\"dependencies\":[{\"features\":[\"derive\",\"env\"],\"name\":\"clap\",\"optional\":true,\"req\":\"^4.1\"},{\"default_features\":false,\"name\":\"console\",\"optional\":true,\"req\":\"^0.15.4\"},{\"name\":\"csv\",\"optional\":true,\"req\":\"^1.1.6\"},{\"name\":\"globset\",\"optional\":true,\"req\":\"^0.4.6\"},{\"name\":\"once_cell\",\"req\":\"^1.20.2\"},{\"name\":\"pest\",\"optional\":true,\"req\":\"^2.1.3\"},{\"name\":\"pest_derive\",\"optional\":true,\"req\":\"^2.1.0\"},{\"default_features\":false,\"features\":[\"std\",\"unicode\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.6.0\"},{\"name\":\"ron\",\"optional\":true,\"req\":\"^0.12.0\"},{\"kind\":\"dev\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.117\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.117\"},{\"features\":[\"inline\"],\"name\":\"similar\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.4.2\"},{\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"serde\",\"parse\",\"display\"],\"name\":\"toml_edit\",\"optional\":true,\"req\":\"^0.23.0\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"walkdir\",\"optional\":true,\"req\":\"^2.3.1\"}],\"features\":{\"_cargo_insta_internal\":[\"clap\"],\"colors\":[\"console\"],\"csv\":[\"dep:csv\",\"serde\"],\"default\":[\"colors\"],\"filters\":[\"regex\"],\"glob\":[\"walkdir\",\"globset\"],\"json\":[\"serde\"],\"redactions\":[\"pest\",\"pest_derive\",\"serde\"],\"ron\":[\"dep:ron\",\"serde\"],\"toml\":[\"dep:toml_edit\",\"dep:toml_writer\",\"serde\"],\"yaml\":[\"serde\"]}}", + "instability_0.3.11": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.23\"},{\"name\":\"indoc\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.86\"},{\"name\":\"quote\",\"req\":\"^1.0.25\"},{\"features\":[\"derive\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0.15\"}],\"features\":{}}", + "intl-memoizer_0.5.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"fluent-langneg\",\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"intl_pluralrules\",\"req\":\"^7.0\"},{\"name\":\"type-map\",\"req\":\"^0.5\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", + "intl_pluralrules_7.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", + "inventory_0.3.21": "{\"dependencies\":[{\"name\":\"rustversion\",\"req\":\"^1.0\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.89\"}],\"features\":{}}", + "io_tee_0.1.1": "{\"dependencies\":[],\"features\":{}}", "ipconfig_0.3.2": "{\"dependencies\":[{\"name\":\"socket2\",\"req\":\"^0.5.1\",\"target\":\"cfg(windows)\"},{\"name\":\"widestring\",\"req\":\"^1.0.2\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_System_Registry\"],\"name\":\"windows-sys\",\"req\":\"^0.48.0\",\"target\":\"cfg(windows)\"},{\"name\":\"winreg\",\"optional\":true,\"req\":\"^0.50.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"computer\":[\"winreg\"],\"default\":[\"computer\"]}}", "ipnet_2.11.0": "{\"dependencies\":[{\"name\":\"heapless\",\"optional\":true,\"req\":\"^0\"},{\"name\":\"schemars\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"package\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"json\":[\"serde\",\"schemars\"],\"ser_as_str\":[\"heapless\"],\"std\":[]}}", - "iri-string_0.7.8": "{\"dependencies\":[{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.104\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"memchr?/std\",\"serde?/std\"]}}", - "is-terminal_0.4.16": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"atty\",\"req\":\"^0.2.14\"},{\"name\":\"hermit-abi\",\"req\":\"^0.5.0\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.110\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"termios\"],\"kind\":\"dev\",\"name\":\"rustix\",\"req\":\"^1.0.0\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"stdio\"],\"kind\":\"dev\",\"name\":\"rustix\",\"req\":\"^1.0.0\",\"target\":\"cfg(not(any(windows, target_os = \\\"hermit\\\", target_os = \\\"unknown\\\")))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_Console\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.60\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "iri-string_0.7.10": "{\"dependencies\":[{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.103\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.104\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"memchr?/std\",\"serde?/std\"]}}", + "is-terminal_0.4.17": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"atty\",\"req\":\"^0.2.14\"},{\"name\":\"hermit-abi\",\"req\":\"^0.5.0\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.110\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"termios\"],\"kind\":\"dev\",\"name\":\"rustix\",\"req\":\"^1.0.0\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"stdio\"],\"kind\":\"dev\",\"name\":\"rustix\",\"req\":\"^1.0.0\",\"target\":\"cfg(not(any(windows, target_os = \\\"hermit\\\", target_os = \\\"unknown\\\")))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_Console\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "is_ci_1.2.0": "{\"dependencies\":[],\"features\":{}}", - "is_terminal_polyfill_1.70.1": "{\"dependencies\":[],\"features\":{\"default\":[]}}", + "is_terminal_polyfill_1.70.2": "{\"dependencies\":[],\"features\":{\"default\":[]}}", "itertools_0.10.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"= 0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.13.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.14.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", - "itoa_1.0.15": "{\"dependencies\":[{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", - "jiff-static_0.2.15": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.93\"},{\"name\":\"quote\",\"req\":\"^1.0.38\"},{\"name\":\"syn\",\"req\":\"^2.0.98\"}],\"features\":{\"default\":[],\"perf-inline\":[],\"tz-fat\":[],\"tzdb\":[\"dep:jiff-tzdb\"]}}", - "jiff_0.2.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.15\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.203\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", + "itoa_1.0.17": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", + "jiff-static_0.2.18": "{\"dependencies\":[{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.93\"},{\"name\":\"quote\",\"req\":\"^1.0.38\"},{\"name\":\"syn\",\"req\":\"^2.0.98\"}],\"features\":{\"default\":[],\"perf-inline\":[],\"tz-fat\":[],\"tzdb\":[\"dep:jiff-tzdb\"]}}", + "jiff_0.2.18": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.18\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", "jni-sys_0.3.0": "{\"dependencies\":[],\"features\":{}}", "jni_0.21.1": "{\"dependencies\":[{\"name\":\"cesu8\",\"req\":\"^1.1.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"combine\",\"req\":\"^4.1.0\"},{\"name\":\"java-locator\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"jni-sys\",\"req\":\"^0.3.0\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"name\":\"thiserror\",\"req\":\"^1.0.20\"},{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rusty-fork\",\"req\":\"^0.3.0\"},{\"kind\":\"build\",\"name\":\"walkdir\",\"req\":\"^2\"},{\"features\":[\"Win32_Globalization\"],\"name\":\"windows-sys\",\"req\":\"^0.45.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.13.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"invocation\":[\"java-locator\",\"libloading\"]}}", "jobserver_0.1.34": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.3.2\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2.171\",\"target\":\"cfg(unix)\"},{\"features\":[\"fs\"],\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.28.0\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"}],\"features\":{}}", - "js-sys_0.3.77": "{\"dependencies\":[{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.100\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"wasm-bindgen/std\"]}}", + "js-sys_0.3.85": "{\"dependencies\":[{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.108\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"wasm-bindgen/std\"]}}", "keyring_3.6.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"name\":\"byteorder\",\"optional\":true,\"req\":\"^1.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"features\":[\"derive\",\"wrap_help\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4\"},{\"name\":\"dbus-secret-service\",\"optional\":true,\"req\":\"^4.0.0-rc.1\",\"target\":\"cfg(target_os = \\\"openbsd\\\")\"},{\"name\":\"dbus-secret-service\",\"optional\":true,\"req\":\"^4.0.0-rc.2\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"dbus-secret-service\",\"optional\":true,\"req\":\"^4.0.1\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11.5\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2\"},{\"features\":[\"std\"],\"name\":\"linux-keyutils\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"log\",\"req\":\"^0.4.22\"},{\"name\":\"openssl\",\"optional\":true,\"req\":\"^0.10.66\"},{\"kind\":\"dev\",\"name\":\"rpassword\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"rprompt\",\"req\":\"^2\"},{\"name\":\"secret-service\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"name\":\"secret-service\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"secret-service\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"openbsd\\\")\"},{\"name\":\"security-framework\",\"optional\":true,\"req\":\"^2\",\"target\":\"cfg(target_os = \\\"ios\\\")\"},{\"name\":\"security-framework\",\"optional\":true,\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"whoami\",\"req\":\"^1.5\"},{\"features\":[\"Win32_Foundation\",\"Win32_Security_Credentials\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\"^0.60\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"zbus\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"name\":\"zbus\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"linux\\\")\"},{\"name\":\"zbus\",\"optional\":true,\"req\":\"^4\",\"target\":\"cfg(target_os = \\\"openbsd\\\")\"},{\"name\":\"zeroize\",\"req\":\"^1.8.1\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"apple-native\":[\"dep:security-framework\"],\"async-io\":[\"zbus?/async-io\"],\"async-secret-service\":[\"dep:secret-service\",\"dep:zbus\"],\"crypto-openssl\":[\"dbus-secret-service?/crypto-openssl\",\"secret-service?/crypto-openssl\"],\"crypto-rust\":[\"dbus-secret-service?/crypto-rust\",\"secret-service?/crypto-rust\"],\"linux-native\":[\"dep:linux-keyutils\"],\"linux-native-async-persistent\":[\"linux-native\",\"async-secret-service\"],\"linux-native-sync-persistent\":[\"linux-native\",\"sync-secret-service\"],\"sync-secret-service\":[\"dep:dbus-secret-service\"],\"tokio\":[\"zbus?/tokio\"],\"vendored\":[\"dbus-secret-service?/vendored\",\"openssl?/vendored\"],\"windows-native\":[\"dep:windows-sys\",\"dep:byteorder\"]}}", "kqueue-sys_1.0.4": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^1.2.1\"},{\"name\":\"libc\",\"req\":\"^0.2.74\"}],\"features\":{}}", "kqueue_1.1.1": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"dhat\",\"req\":\"^0.3.2\"},{\"name\":\"kqueue-sys\",\"req\":\"^1.0.4\"},{\"name\":\"libc\",\"req\":\"^0.2.17\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"}],\"features\":{}}", @@ -881,18 +909,18 @@ "landlock_0.4.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"enumflags2\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2.175\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.26\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.26\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"}],\"features\":{}}", "language-tags_0.3.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{}}", "lazy_static_1.5.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"once\"],\"name\":\"spin\",\"optional\":true,\"req\":\"^0.9.8\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{\"spin_no_std\":[\"spin\"]}}", - "libc_0.2.177": "{\"dependencies\":[{\"name\":\"rustc-std-workspace-core\",\"optional\":true,\"req\":\"^1.0.1\"}],\"features\":{\"align\":[],\"const-extern-fn\":[],\"default\":[\"std\"],\"extra_traits\":[],\"rustc-dep-of-std\":[\"align\",\"rustc-std-workspace-core\"],\"std\":[],\"use_std\":[\"std\"]}}", - "libdbus-sys_0.2.6": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.0.78\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"default\":[\"pkg-config\"],\"vendored\":[\"cc\"]}}", + "libc_0.2.180": "{\"dependencies\":[{\"name\":\"rustc-std-workspace-core\",\"optional\":true,\"req\":\"^1.0.1\"}],\"features\":{\"align\":[],\"const-extern-fn\":[],\"default\":[\"std\"],\"extra_traits\":[],\"rustc-dep-of-std\":[\"align\",\"rustc-std-workspace-core\"],\"std\":[],\"use_std\":[\"std\"]}}", + "libdbus-sys_0.2.7": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.0.78\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"default\":[\"pkg-config\"],\"vendored\":[\"cc\"]}}", "libloading_0.8.9": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "libm_0.2.16": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"no-panic\",\"req\":\"^0.1.35\"}],\"features\":{\"arch\":[],\"default\":[\"arch\"],\"force-soft-floats\":[],\"unstable\":[\"unstable-intrinsics\",\"unstable-float\"],\"unstable-float\":[],\"unstable-intrinsics\":[],\"unstable-public-internals\":[]}}", - "libredox_0.1.6": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"ioslice\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"redox_syscall\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"call\":[],\"default\":[\"call\",\"std\",\"redox_syscall\"],\"mkns\":[\"ioslice\"],\"std\":[]}}", + "libredox_0.1.12": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"ioslice\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"redox_syscall\",\"optional\":true,\"req\":\"^0.7\"}],\"features\":{\"call\":[],\"default\":[\"call\",\"std\",\"redox_syscall\"],\"mkns\":[\"ioslice\"],\"std\":[]}}", "libsqlite3-sys_0.30.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.69\"},{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1.1.6\"},{\"name\":\"openssl-sys\",\"optional\":true,\"req\":\"^0.9.103\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"optional\":true,\"req\":\"^0.3.19\"},{\"kind\":\"build\",\"name\":\"prettyplease\",\"optional\":true,\"req\":\"^0.2.20\"},{\"default_features\":false,\"kind\":\"build\",\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.36\"},{\"features\":[\"full\",\"extra-traits\",\"visit-mut\"],\"kind\":\"build\",\"name\":\"syn\",\"optional\":true,\"req\":\"^2.0.72\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"optional\":true,\"req\":\"^0.2.15\"}],\"features\":{\"buildtime_bindgen\":[\"bindgen\",\"pkg-config\",\"vcpkg\"],\"bundled\":[\"cc\",\"bundled_bindings\"],\"bundled-sqlcipher\":[\"bundled\"],\"bundled-sqlcipher-vendored-openssl\":[\"bundled-sqlcipher\",\"openssl-sys/vendored\"],\"bundled-windows\":[\"cc\",\"bundled_bindings\"],\"bundled_bindings\":[],\"default\":[\"min_sqlite_version_3_14_0\"],\"in_gecko\":[],\"loadable_extension\":[\"prettyplease\",\"quote\",\"syn\"],\"min_sqlite_version_3_14_0\":[\"pkg-config\",\"vcpkg\"],\"preupdate_hook\":[\"buildtime_bindgen\"],\"session\":[\"preupdate_hook\",\"buildtime_bindgen\"],\"sqlcipher\":[],\"unlock_notify\":[],\"wasm32-wasi-vfs\":[],\"with-asan\":[]}}", "linux-keyutils_0.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"default_features\":false,\"features\":[\"std\",\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.4.11\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.132\"},{\"kind\":\"dev\",\"name\":\"zeroize\",\"req\":\"^1.5.7\"}],\"features\":{\"default\":[],\"std\":[\"bitflags/std\"]}}", + "linux-raw-sys_0.11.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"auxvec\":[],\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", "linux-raw-sys_0.4.15": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"compiler_builtins\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", - "linux-raw-sys_0.9.4": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.100\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"bootparam\":[],\"btrfs\":[],\"default\":[\"std\",\"general\",\"errno\"],\"elf\":[],\"elf_uapi\":[],\"errno\":[],\"general\":[],\"if_arp\":[],\"if_ether\":[],\"if_packet\":[],\"image\":[],\"io_uring\":[],\"ioctl\":[],\"landlock\":[],\"loop_device\":[],\"mempolicy\":[],\"net\":[],\"netlink\":[],\"no_std\":[],\"prctl\":[],\"ptrace\":[],\"rustc-dep-of-std\":[\"core\",\"compiler_builtins\",\"no_std\"],\"std\":[],\"system\":[],\"xdp\":[]}}", - "litemap_0.8.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"use-std\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"features\":[\"validation\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.110\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.110\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde\",\"alloc\"],\"testing\":[\"alloc\"],\"yoke\":[\"dep:yoke\"]}}", + "litemap_0.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"use-std\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"features\":[\"validation\"],\"kind\":\"dev\",\"name\":\"rkyv\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde_core\",\"alloc\"],\"testing\":[\"alloc\"],\"yoke\":[\"dep:yoke\"]}}", "local-waker_0.1.4": "{\"dependencies\":[],\"features\":{}}", - "lock_api_0.4.13": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1.1.0\"},{\"name\":\"owning_ref\",\"optional\":true,\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"scopeguard\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.126\"}],\"features\":{\"arc_lock\":[],\"atomic_usize\":[],\"default\":[\"atomic_usize\"],\"nightly\":[]}}", + "lock_api_0.4.14": "{\"dependencies\":[{\"name\":\"owning_ref\",\"optional\":true,\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"scopeguard\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.126\"}],\"features\":{\"arc_lock\":[],\"atomic_usize\":[],\"default\":[\"atomic_usize\"],\"nightly\":[]}}", "log_0.4.29": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.63\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"sval\",\"optional\":true,\"req\":\"^2.16\"},{\"kind\":\"dev\",\"name\":\"sval\",\"req\":\"^2.16\"},{\"kind\":\"dev\",\"name\":\"sval_derive\",\"req\":\"^2.16\"},{\"default_features\":false,\"name\":\"sval_ref\",\"optional\":true,\"req\":\"^2.16\"},{\"default_features\":false,\"features\":[\"inline-i128\"],\"name\":\"value-bag\",\"optional\":true,\"req\":\"^1.12\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"value-bag\",\"req\":\"^1.12\"}],\"features\":{\"kv\":[],\"kv_serde\":[\"kv_std\",\"value-bag/serde\",\"serde\"],\"kv_std\":[\"std\",\"kv\",\"value-bag/error\"],\"kv_sval\":[\"kv\",\"value-bag/sval\",\"sval\",\"sval_ref\"],\"kv_unstable\":[\"kv\",\"value-bag\"],\"kv_unstable_serde\":[\"kv_serde\",\"kv_unstable_std\"],\"kv_unstable_std\":[\"kv_std\",\"kv_unstable\"],\"kv_unstable_sval\":[\"kv_sval\",\"kv_unstable\"],\"max_level_debug\":[],\"max_level_error\":[],\"max_level_info\":[],\"max_level_off\":[],\"max_level_trace\":[],\"max_level_warn\":[],\"release_max_level_debug\":[],\"release_max_level_error\":[],\"release_max_level_info\":[],\"release_max_level_off\":[],\"release_max_level_trace\":[],\"release_max_level_warn\":[],\"serde\":[\"serde_core\"],\"std\":[]}}", "logos-derive_0.12.1": "{\"dependencies\":[{\"name\":\"beef\",\"req\":\"^0.5.0\"},{\"name\":\"fnv\",\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^0.6.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.9\"},{\"name\":\"quote\",\"req\":\"^1.0.3\"},{\"name\":\"regex-syntax\",\"req\":\"^0.6\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^1.0.17\"}],\"features\":{}}", "logos_0.12.1": "{\"dependencies\":[{\"name\":\"logos-derive\",\"optional\":true,\"req\":\"^0.12.1\"}],\"features\":{\"default\":[\"export_derive\",\"std\"],\"export_derive\":[\"logos-derive\"],\"std\":[]}}", @@ -901,22 +929,24 @@ "lru_0.12.5": "{\"dependencies\":[{\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"scoped_threadpool\",\"req\":\"0.1.*\"},{\"kind\":\"dev\",\"name\":\"stats_alloc\",\"req\":\"0.1.*\"}],\"features\":{\"default\":[\"hashbrown\"],\"nightly\":[\"hashbrown\",\"hashbrown/nightly\"]}}", "lru_0.16.3": "{\"dependencies\":[{\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.16.0\"},{\"kind\":\"dev\",\"name\":\"scoped_threadpool\",\"req\":\"0.1.*\"},{\"kind\":\"dev\",\"name\":\"stats_alloc\",\"req\":\"0.1.*\"}],\"features\":{\"default\":[\"hashbrown\"],\"nightly\":[\"hashbrown\",\"hashbrown/nightly\"]}}", "lsp-types_0.94.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^1.0.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.34\"},{\"name\":\"serde_json\",\"req\":\"^1.0.50\"},{\"name\":\"serde_repr\",\"req\":\"^0.1\"},{\"features\":[\"serde\"],\"name\":\"url\",\"req\":\"^2.0.0\"}],\"features\":{\"default\":[],\"proposed\":[]}}", + "lzma-rs_0.3.0": "{\"dependencies\":[{\"name\":\"byteorder\",\"req\":\"^1.4.3\"},{\"name\":\"crc\",\"req\":\"^3.0.0\"},{\"name\":\"env_logger\",\"optional\":true,\"req\":\"^0.9.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"kind\":\"dev\",\"name\":\"rust-lzma\",\"req\":\"^0.5\"}],\"features\":{\"enable_logging\":[\"env_logger\",\"log\"],\"raw_decoder\":[],\"stream\":[]}}", + "lzma-sys_0.1.20": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.34\"},{\"name\":\"libc\",\"req\":\"^0.2.51\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.14\"}],\"features\":{\"static\":[]}}", "maplit_1.0.2": "{\"dependencies\":[],\"features\":{}}", "matchers_0.2.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"syntax\",\"dfa-build\",\"dfa-search\"],\"name\":\"regex-automata\",\"req\":\"^0.4\"}],\"features\":{\"unicode\":[\"regex-automata/unicode\"]}}", "matchit_0.8.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.4\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.2.2\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.5.4\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5.2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.4\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", "matchit_0.9.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"http1\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"kind\":\"dev\",\"name\":\"wayfind\",\"req\":\"^0.8\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", "md-5_0.10.6": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"md5-asm\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(any(target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"}],\"features\":{\"asm\":[\"md5-asm\"],\"default\":[\"std\"],\"force-soft\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "md5_0.8.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "memchr_2.7.5": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"libc\":[],\"logging\":[\"dep:log\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[\"alloc\"],\"use_std\":[\"std\"]}}", + "memchr_2.7.6": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"libc\":[],\"logging\":[\"dep:log\"],\"rustc-dep-of-std\":[\"core\"],\"std\":[\"alloc\"],\"use_std\":[\"std\"]}}", "memoffset_0.6.5": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"unstable_const\":[]}}", "memoffset_0.9.1": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"unstable_const\":[],\"unstable_offset_of\":[]}}", "mime_0.3.17": "{\"dependencies\":[],\"features\":{}}", "mime_guess_2.0.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"mime\",\"req\":\"^0.3\"},{\"name\":\"unicase\",\"req\":\"^2.4.0\"},{\"kind\":\"build\",\"name\":\"unicase\",\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"rev-mappings\"],\"rev-mappings\":[]}}", "minimal-lexical_0.2.1": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"compact\":[],\"default\":[\"std\"],\"lint\":[],\"nightly\":[],\"std\":[]}}", "miniz_oxide_0.8.9": "{\"dependencies\":[{\"default_features\":false,\"name\":\"adler2\",\"req\":\"^2.0\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"simd-adler32\",\"optional\":true,\"req\":\"^0.3.3\"}],\"features\":{\"block-boundary\":[],\"default\":[\"with-alloc\"],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"adler2/rustc-dep-of-std\"],\"simd\":[\"simd-adler32\"],\"std\":[],\"with-alloc\":[]}}", - "mio_1.0.4": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"name\":\"libc\",\"req\":\"^0.2.159\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.159\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.159\",\"target\":\"cfg(unix)\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"wasi\",\"req\":\"^0.11.0\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"features\":[\"Wdk_Foundation\",\"Wdk_Storage_FileSystem\",\"Wdk_System_IO\",\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_Storage_FileSystem\",\"Win32_System_IO\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"log\"],\"net\":[],\"os-ext\":[\"os-poll\",\"windows-sys/Win32_System_Pipes\",\"windows-sys/Win32_Security\"],\"os-poll\":[]}}", - "moka_0.12.12": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2.8\"},{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8.3\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.19\"},{\"name\":\"async-lock\",\"optional\":true,\"req\":\"^3.3\"},{\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-epoch\",\"req\":\"^0.9.18\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10.0\"},{\"name\":\"equivalent\",\"req\":\"^1.0\"},{\"name\":\"event-listener\",\"optional\":true,\"req\":\"^5.3\"},{\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(moka_loom)\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.7\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"portable-atomic\",\"req\":\"^1.6\"},{\"name\":\"quanta\",\"optional\":true,\"req\":\"^0.12.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"default_features\":false,\"features\":[\"rustls-tls\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\"},{\"name\":\"smallvec\",\"req\":\"^1.8\"},{\"name\":\"tagptr\",\"req\":\"^0.2\"},{\"features\":[\"fs\",\"io-util\",\"macros\",\"rt-multi-thread\",\"sync\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\",\"target\":\"cfg(trybuild)\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"req\":\"^1.1\"}],\"features\":{\"atomic64\":[],\"default\":[],\"future\":[\"async-lock\",\"event-listener\",\"futures-util\"],\"logging\":[\"log\"],\"quanta\":[\"dep:quanta\"],\"sync\":[],\"unstable-debug-counters\":[\"future\"]}}", - "moxcms_0.7.5": "{\"dependencies\":[{\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"pxfm\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"avx\":[],\"avx512\":[],\"default\":[\"avx\",\"sse\",\"neon\"],\"neon\":[],\"options\":[],\"sse\":[]}}", + "mio_1.1.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"libc\",\"req\":\"^0.2.178\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.178\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.178\",\"target\":\"cfg(unix)\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"name\":\"wasi\",\"req\":\"^0.11.0\",\"target\":\"cfg(target_os = \\\"wasi\\\")\"},{\"features\":[\"Wdk_Foundation\",\"Wdk_Storage_FileSystem\",\"Wdk_System_IO\",\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_Storage_FileSystem\",\"Win32_Security\",\"Win32_System_IO\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"log\"],\"net\":[],\"os-ext\":[\"os-poll\",\"windows-sys/Win32_System_Pipes\",\"windows-sys/Win32_Security\"],\"os-poll\":[]}}", + "moka_0.12.13": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2.8\"},{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8.3\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.19\"},{\"name\":\"async-lock\",\"optional\":true,\"req\":\"^3.3\"},{\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-epoch\",\"req\":\"^0.9.18\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10.0\"},{\"name\":\"equivalent\",\"req\":\"^1.0\"},{\"name\":\"event-listener\",\"optional\":true,\"req\":\"^5.3\"},{\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(moka_loom)\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.7\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"portable-atomic\",\"req\":\"^1.6\"},{\"name\":\"quanta\",\"optional\":true,\"req\":\"^0.12.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"default_features\":false,\"features\":[\"rustls-tls\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\"},{\"name\":\"smallvec\",\"req\":\"^1.8\"},{\"name\":\"tagptr\",\"req\":\"^0.2\"},{\"features\":[\"fs\",\"io-util\",\"macros\",\"rt-multi-thread\",\"sync\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\",\"target\":\"cfg(trybuild)\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"req\":\"^1.1\"}],\"features\":{\"atomic64\":[],\"default\":[],\"future\":[\"async-lock\",\"event-listener\",\"futures-util\"],\"logging\":[\"log\"],\"quanta\":[\"dep:quanta\"],\"sync\":[],\"unstable-debug-counters\":[\"future\"]}}", + "moxcms_0.7.11": "{\"dependencies\":[{\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"pxfm\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"avx\":[],\"avx512\":[],\"default\":[\"avx\",\"sse\",\"neon\"],\"neon\":[],\"options\":[],\"sse\":[]}}", "multimap_0.10.1": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"serde_impl\"],\"serde_impl\":[\"serde\"]}}", "native-tls_0.2.14": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"name\":\"log\",\"req\":\"^0.4.5\",\"target\":\"cfg(not(any(target_os = \\\"windows\\\", target_vendor = \\\"apple\\\")))\"},{\"name\":\"openssl\",\"req\":\"^0.10.69\",\"target\":\"cfg(not(any(target_os = \\\"windows\\\", target_vendor = \\\"apple\\\")))\"},{\"name\":\"openssl-probe\",\"req\":\"^0.1\",\"target\":\"cfg(not(any(target_os = \\\"windows\\\", target_vendor = \\\"apple\\\")))\"},{\"name\":\"openssl-sys\",\"req\":\"^0.9.81\",\"target\":\"cfg(not(any(target_os = \\\"windows\\\", target_vendor = \\\"apple\\\")))\"},{\"name\":\"schannel\",\"req\":\"^0.1.17\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"security-framework\",\"req\":\"^2.0.0\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"name\":\"security-framework-sys\",\"req\":\"^2.0.0\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"name\":\"tempfile\",\"req\":\"^3.1.0\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"test-cert-gen\",\"req\":\"^0.9\"}],\"features\":{\"alpn\":[\"security-framework/alpn\"],\"vendored\":[\"openssl/vendored\"]}}", "ndk-context_0.1.1": "{\"dependencies\":[],\"features\":{}}", @@ -928,13 +958,13 @@ "nom_7.1.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.3\"},{\"default_features\":false,\"name\":\"minimal-lexical\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.0.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"docsrs\":[],\"std\":[\"alloc\",\"memchr/std\",\"minimal-lexical/std\"]}}", "nom_8.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.3\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"=1.0.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"docsrs\":[],\"std\":[\"alloc\",\"memchr/std\"]}}", "normalize-line-endings_0.3.0": "{\"dependencies\":[],\"features\":{}}", - "notify-types_2.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.34.0\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.24.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.89\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.39\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1.0\"}],\"features\":{\"serialization-compat-6\":[]}}", + "notify-types_2.1.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.34.0\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.26.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.89\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.39\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1.0\"}],\"features\":{\"serde\":[\"dep:serde\",\"bitflags/serde\"],\"serialization-compat-6\":[]}}", "notify_8.2.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.7.0\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"name\":\"crossbeam-channel\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"flume\",\"optional\":true,\"req\":\"^0.11.1\"},{\"name\":\"fsevent-sys\",\"optional\":true,\"req\":\"^4.0.0\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"default_features\":false,\"name\":\"inotify\",\"req\":\"^0.11.0\",\"target\":\"cfg(any(target_os=\\\"linux\\\", target_os=\\\"android\\\"))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.34.0\"},{\"name\":\"kqueue\",\"req\":\"^1.1.1\",\"target\":\"cfg(any(target_os=\\\"freebsd\\\", target_os=\\\"openbsd\\\", target_os = \\\"netbsd\\\", target_os = \\\"dragonflybsd\\\", target_os = \\\"ios\\\"))\"},{\"name\":\"kqueue\",\"optional\":true,\"req\":\"^1.1.1\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.4\"},{\"name\":\"log\",\"req\":\"^0.4.17\"},{\"features\":[\"os-ext\"],\"name\":\"mio\",\"req\":\"^1.0\",\"target\":\"cfg(any(target_os=\\\"freebsd\\\", target_os=\\\"openbsd\\\", target_os = \\\"netbsd\\\", target_os = \\\"dragonflybsd\\\", target_os = \\\"ios\\\"))\"},{\"features\":[\"os-ext\"],\"name\":\"mio\",\"req\":\"^1.0\",\"target\":\"cfg(any(target_os=\\\"linux\\\", target_os=\\\"android\\\"))\"},{\"features\":[\"os-ext\"],\"name\":\"mio\",\"optional\":true,\"req\":\"^1.0\",\"target\":\"cfg(target_os=\\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.29.0\"},{\"name\":\"notify-types\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.39\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.0\"},{\"kind\":\"dev\",\"name\":\"trash\",\"req\":\"^5.2.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"walkdir\",\"req\":\"^2.4.0\"},{\"features\":[\"Win32_System_Threading\",\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_Security\",\"Win32_System_WindowsProgramming\",\"Win32_System_IO\"],\"name\":\"windows-sys\",\"req\":\"^0.60.1\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"macos_fsevent\"],\"macos_fsevent\":[\"fsevent-sys\"],\"macos_kqueue\":[\"kqueue\",\"mio\"],\"serde\":[\"notify-types/serde\"],\"serialization-compat-6\":[\"notify-types/serialization-compat-6\"]}}", - "nu-ansi-term_0.50.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.152\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.94\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\",\"Win32_Storage_FileSystem\",\"Win32_Security\"],\"name\":\"windows\",\"package\":\"windows-sys\",\"req\":\"^0.52.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"derive_serde_style\":[\"serde\"],\"gnu_legacy\":[]}}", + "nu-ansi-term_0.50.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.152\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.94\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\",\"Win32_Storage_FileSystem\",\"Win32_Security\"],\"name\":\"windows\",\"package\":\"windows-sys\",\"req\":\">=0.59, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"std\"],\"derive_serde_style\":[\"serde\"],\"gnu_legacy\":[],\"std\":[]}}", "num-bigint-dig_0.8.6": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"spin_no_std\"],\"name\":\"lazy_static\",\"req\":\"^1.2.0\"},{\"name\":\"libm\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-integer\",\"req\":\"^0.1.39\"},{\"default_features\":false,\"name\":\"num-iter\",\"req\":\"^0.1.37\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-traits\",\"req\":\"^0.2.4\"},{\"default_features\":false,\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8.3\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand_chacha\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand_isaac\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"smallvec\",\"req\":\"^1.10.0\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.5\"}],\"features\":{\"default\":[\"std\",\"u64_digit\"],\"fuzz\":[\"arbitrary\",\"smallvec/arbitrary\"],\"i128\":[],\"nightly\":[],\"prime\":[\"rand/std_rng\"],\"std\":[\"num-integer/std\",\"num-traits/std\",\"smallvec/write\",\"rand/std\",\"serde/std\"],\"u64_digit\":[]}}", "num-bigint_0.4.6": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-integer\",\"req\":\"^0.1.46\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-traits\",\"req\":\"^0.2.18\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"std\"],\"quickcheck\":[\"dep:quickcheck\"],\"rand\":[\"dep:rand\"],\"serde\":[\"dep:serde\"],\"std\":[\"num-integer/std\",\"num-traits/std\"]}}", "num-complex_0.4.6": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytecheck\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-traits\",\"req\":\"^0.2.18\"},{\"default_features\":false,\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"bytecheck\":[\"dep:bytecheck\"],\"bytemuck\":[\"dep:bytemuck\"],\"default\":[\"std\"],\"libm\":[\"num-traits/libm\"],\"rand\":[\"dep:rand\"],\"rkyv\":[\"dep:rkyv\"],\"serde\":[\"dep:serde\"],\"std\":[\"num-traits/std\"]}}", - "num-conv_0.1.0": "{\"dependencies\":[],\"features\":{}}", + "num-conv_0.2.0": "{\"dependencies\":[],\"features\":{}}", "num-integer_0.1.46": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-traits\",\"req\":\"^0.2.11\"}],\"features\":{\"default\":[\"std\"],\"i128\":[],\"std\":[\"num-traits/std\"]}}", "num-iter_0.1.45": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"autocfg\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-integer\",\"req\":\"^0.1.46\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-traits\",\"req\":\"^0.2.11\"}],\"features\":{\"default\":[\"std\"],\"i128\":[],\"std\":[\"num-integer/std\",\"num-traits/std\"]}}", "num-rational_0.4.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-integer\",\"req\":\"^0.1.42\"},{\"default_features\":false,\"features\":[\"i128\"],\"name\":\"num-traits\",\"req\":\"^0.2.18\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"num-bigint\",\"std\"],\"num-bigint\":[\"dep:num-bigint\"],\"num-bigint-std\":[\"num-bigint/std\"],\"serde\":[\"dep:serde\"],\"std\":[\"num-bigint?/std\",\"num-integer/std\",\"num-traits/std\"]}}", @@ -943,21 +973,31 @@ "num_cpus_1.17.0": "{\"dependencies\":[{\"name\":\"hermit-abi\",\"req\":\"^0.5.0\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"name\":\"libc\",\"req\":\"^0.2.26\",\"target\":\"cfg(not(windows))\"}],\"features\":{}}", "num_threads_0.1.7": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.107\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"ios\\\", target_os = \\\"freebsd\\\"))\"}],\"features\":{}}", "oauth2_5.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.13\"},{\"name\":\"base64\",\"req\":\">=0.21, <0.23\"},{\"default_features\":false,\"features\":[\"clock\",\"serde\",\"std\",\"wasmbind\"],\"name\":\"chrono\",\"req\":\"^0.4.31\"},{\"name\":\"curl\",\"optional\":true,\"req\":\"^0.4.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"js\"],\"name\":\"getrandom\",\"req\":\"^0.2\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"rand\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"serde_path_to_error\",\"req\":\"^0.1.2\"},{\"name\":\"sha2\",\"req\":\"^0.10\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"name\":\"ureq\",\"optional\":true,\"req\":\"^2\"},{\"features\":[\"serde\"],\"name\":\"url\",\"req\":\"^2.1\"},{\"features\":[\"v4\"],\"kind\":\"dev\",\"name\":\"uuid\",\"req\":\"^1.10\"}],\"features\":{\"default\":[\"reqwest\",\"rustls-tls\"],\"native-tls\":[\"reqwest/native-tls\"],\"pkce-plain\":[],\"reqwest-blocking\":[\"reqwest/blocking\"],\"rustls-tls\":[\"reqwest/rustls-tls\"],\"timing-resistant-secret-traits\":[]}}", - "objc2-app-kit_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"CKContainer\",\"CKRecord\",\"CKShare\",\"CKShareMetadata\"],\"name\":\"objc2-cloud-kit\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"NSAttributeDescription\",\"NSEntityDescription\",\"NSFetchRequest\",\"NSManagedObjectContext\",\"NSManagedObjectModel\",\"NSPersistentStoreRequest\",\"NSPropertyDescription\"],\"name\":\"objc2-core-data\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CFCGTypes\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"CGColor\",\"CGColorSpace\",\"CGContext\",\"CGEventTypes\",\"CGFont\",\"CGImage\",\"CGPath\",\"objc2\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CIColor\",\"CIContext\",\"CIFilter\",\"CIImage\"],\"name\":\"objc2-core-image\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"CADisplayLink\",\"CALayer\",\"CAMediaTimingFunction\"],\"name\":\"objc2-quartz-core\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"UTType\"],\"name\":\"objc2-uniform-type-identifiers\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"}],\"features\":{\"AppKitDefines\":[],\"AppKitErrors\":[],\"NSATSTypesetter\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/objc2-core-foundation\"],\"NSAccessibility\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSAccessibilityColor\":[\"objc2-foundation/NSString\"],\"NSAccessibilityConstants\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAccessibilityCustomAction\":[\"objc2-foundation/NSString\"],\"NSAccessibilityCustomRotor\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSAccessibilityElement\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSAccessibilityProtocols\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSActionCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAdaptiveImageGlyph\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAffineTransform\":[\"objc2-foundation/NSAffineTransform\"],\"NSAlert\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"NSAlignmentFeedbackFilter\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/objc2-core-foundation\"],\"NSAnimation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSAnimationContext\":[\"objc2-foundation/NSDate\"],\"NSAppearance\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAppleScriptExtensions\":[\"objc2-foundation/NSAppleScript\",\"objc2-foundation/NSAttributedString\"],\"NSApplication\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSException\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUserActivity\"],\"NSApplicationScripting\":[\"objc2-foundation/NSArray\"],\"NSArrayController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"NSAttributedString\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSBezierPath\":[\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSBitmapImageRep\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSBox\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSBrowser\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSBrowserCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSButton\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSButtonCell\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSButtonTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSCIImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCachedImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCandidateListTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSCell\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSFormatter\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSClickGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSClipView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewCompositionalLayout\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewFlowLayout\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewGridLayout\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewLayout\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewTransitionLayout\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSColor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorList\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSColorPanel\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorPicker\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorPickerTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSColorPicking\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorSampler\":[],\"NSColorSpace\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSColorWell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSComboBox\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSComboBoxCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSComboButton\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSFormatter\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSController\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSCursor\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCustomImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCustomTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDataAsset\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDatePicker\":[\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSTimeZone\",\"objc2-foundation/objc2-core-foundation\"],\"NSDatePickerCell\":[\"bitflags\",\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTimeZone\"],\"NSDictionaryController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDiffableDataSource\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDirection\":[\"bitflags\"],\"NSDockTile\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSDocument\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFilePresenter\",\"objc2-foundation/NSFileVersion\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\"],\"NSDocumentController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSDocumentScripting\":[\"objc2-foundation/NSScriptCommand\",\"objc2-foundation/NSScriptObjectSpecifiers\",\"objc2-foundation/NSScriptStandardSuiteCommands\",\"objc2-foundation/NSString\"],\"NSDragging\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSDraggingItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSDraggingSession\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSDrawer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSEPSImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSErrors\":[\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSString\"],\"NSEvent\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSFilePromiseProvider\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSFilePromiseReceiver\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSFileWrapperExtensions\":[\"objc2-foundation/NSFileWrapper\"],\"NSFont\":[\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSFontAssetRequest\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSProgress\"],\"NSFontCollection\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSFontDescriptor\":[\"bitflags\",\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSFontManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSFontPanel\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSForm\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSFormCell\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSGlyphGenerator\":[\"objc2-foundation/NSAttributedString\"],\"NSGlyphInfo\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSGradient\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSGraphics\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGraphicsContext\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGridView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/objc2-core-foundation\"],\"NSGroupTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSHapticFeedback\":[],\"NSHelpManager\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSImage\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSImageCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSImageRep\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSImageView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSInputManager\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSInputServer\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSInterfaceStyle\":[\"objc2-foundation/NSString\"],\"NSItemProvider\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSKeyValueBinding\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSLayoutAnchor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSLayoutConstraint\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSLayoutGuide\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSLayoutManager\":[\"bitflags\",\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSLevelIndicator\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSLevelIndicatorCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSMagnificationGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSMatrix\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSMediaLibraryBrowserController\":[\"bitflags\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/objc2-core-foundation\"],\"NSMenu\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSZone\",\"objc2-foundation/objc2-core-foundation\"],\"NSMenuItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSMenuItemBadge\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSMenuItemCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSMenuToolbarItem\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSMovie\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSNib\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSNibConnector\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSNibControlConnector\":[\"objc2-foundation/NSObject\"],\"NSNibDeclarations\":[],\"NSNibLoading\":[],\"NSNibOutletConnector\":[\"objc2-foundation/NSObject\"],\"NSObjectController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"NSOpenGL\":[],\"NSOpenGLLayer\":[],\"NSOpenGLView\":[],\"NSOpenPanel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSOutlineView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPDFImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPDFInfo\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPDFPanel\":[\"bitflags\",\"objc2-foundation/NSString\"],\"NSPICTImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPageController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPageLayout\":[\"objc2-foundation/NSArray\"],\"NSPanGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPanel\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSParagraphStyle\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPasteboard\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPasteboardItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSPathCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPathComponentCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPathControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPathControlItem\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPersistentDocument\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFilePresenter\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPickerTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPopUpButton\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPopUpButtonCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPopover\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPopoverTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPredicateEditor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPredicateEditorRowTemplate\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSComparisonPredicate\",\"objc2-foundation/NSExpression\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSPressGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"NSPressureConfiguration\":[],\"NSPreviewRepresentingActivityItem\":[\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\"],\"NSPrintInfo\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPrintOperation\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPrintPanel\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSPrinter\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSProgressIndicator\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\",\"objc2-foundation/objc2-core-foundation\"],\"NSResponder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUndoManager\"],\"NSRotationGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSRuleEditor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSRulerMarker\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSRulerView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSRunningApplication\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSavePanel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSScreen\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrollView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScroller\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrubber\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrubberItemView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrubberLayout\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/objc2-core-foundation\"],\"NSSearchField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSearchFieldCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSearchToolbarItem\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSecureTextField\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSegmentedCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSegmentedControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSShadow\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSSharingCollaborationModeRestriction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSharingService\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSSharingServicePickerToolbarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSharingServicePickerTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSlider\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSliderAccessory\":[\"objc2-foundation/NSObject\"],\"NSSliderCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSliderTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSound\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSpeechRecognizer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSSpeechSynthesizer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSpellChecker\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSOrthography\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\",\"objc2-foundation/objc2-core-foundation\"],\"NSSpellProtocol\":[],\"NSSplitView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSplitViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSplitViewItem\":[\"objc2-foundation/NSObject\"],\"NSStackView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSStatusBar\":[],\"NSStatusBarButton\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSStatusItem\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSStepper\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSStepperCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSStepperTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSFormatter\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSStoryboard\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSString\"],\"NSStoryboardSegue\":[\"objc2-foundation/NSString\"],\"NSStringDrawing\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSwitch\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTabView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTabViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTabViewItem\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableCellView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableColumn\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"NSTableHeaderCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableHeaderView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableRowView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSEnumerator\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableViewDiffableDataSource\":[],\"NSTableViewRowAction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSText\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextAlternatives\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextAttachment\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextAttachmentCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextCheckingClient\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextCheckingController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\"],\"NSTextContainer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextContent\":[\"objc2-foundation/NSString\"],\"NSTextContentManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\"],\"NSTextField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextFieldCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextFinder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextInputClient\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextInputContext\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"NSTextInsertionIndicator\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextLayoutFragment\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\"],\"NSTextLayoutManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"NSTextLineFragment\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextList\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextListElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSTextRange\":[\"objc2-foundation/NSObjCRuntime\"],\"NSTextSelection\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextSelectionNavigation\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSTextStorage\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextStorageScripting\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\"],\"NSTextTable\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOrthography\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextViewportLayoutController\":[],\"NSTintConfiguration\":[\"objc2-foundation/NSObject\"],\"NSTitlebarAccessoryViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTokenField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTokenFieldCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSToolbar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSToolbarItem\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSToolbarItemGroup\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTouch\":[\"bitflags\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTouchBar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTrackingArea\":[\"bitflags\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTrackingSeparatorToolbarItem\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTreeController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"NSTreeNode\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSSortDescriptor\"],\"NSTypesetter\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSUserActivity\":[\"objc2-foundation/NSString\",\"objc2-foundation/NSUserActivity\"],\"NSUserDefaultsController\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUserDefaults\"],\"NSUserInterfaceCompression\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSUserInterfaceItemIdentification\":[\"objc2-foundation/NSString\"],\"NSUserInterfaceItemSearching\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSUserInterfaceLayout\":[],\"NSUserInterfaceValidation\":[],\"NSView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSViewController\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSExtensionContext\",\"objc2-foundation/NSExtensionRequestHandling\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSVisualEffectView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSWindow\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSWindowController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSWindowRestoration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"NSWindowScripting\":[\"objc2-foundation/NSScriptCommand\",\"objc2-foundation/NSScriptStandardSuiteCommands\"],\"NSWindowTab\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"NSWindowTabGroup\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSWorkspace\":[\"bitflags\",\"objc2-foundation/NSAppleEventDescriptor\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileManager\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSWritingToolsCoordinator\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSUUID\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSWritingToolsCoordinatorAnimationParameters\":[],\"NSWritingToolsCoordinatorContext\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSUUID\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"AppKitDefines\",\"AppKitErrors\",\"NSATSTypesetter\",\"NSAccessibility\",\"NSAccessibilityColor\",\"NSAccessibilityConstants\",\"NSAccessibilityCustomAction\",\"NSAccessibilityCustomRotor\",\"NSAccessibilityElement\",\"NSAccessibilityProtocols\",\"NSActionCell\",\"NSAdaptiveImageGlyph\",\"NSAffineTransform\",\"NSAlert\",\"NSAlignmentFeedbackFilter\",\"NSAnimation\",\"NSAnimationContext\",\"NSAppearance\",\"NSAppleScriptExtensions\",\"NSApplication\",\"NSApplicationScripting\",\"NSArrayController\",\"NSAttributedString\",\"NSBezierPath\",\"NSBitmapImageRep\",\"NSBox\",\"NSBrowser\",\"NSBrowserCell\",\"NSButton\",\"NSButtonCell\",\"NSButtonTouchBarItem\",\"NSCIImageRep\",\"NSCachedImageRep\",\"NSCandidateListTouchBarItem\",\"NSCell\",\"NSClickGestureRecognizer\",\"NSClipView\",\"NSCollectionView\",\"NSCollectionViewCompositionalLayout\",\"NSCollectionViewFlowLayout\",\"NSCollectionViewGridLayout\",\"NSCollectionViewLayout\",\"NSCollectionViewTransitionLayout\",\"NSColor\",\"NSColorList\",\"NSColorPanel\",\"NSColorPicker\",\"NSColorPickerTouchBarItem\",\"NSColorPicking\",\"NSColorSampler\",\"NSColorSpace\",\"NSColorWell\",\"NSComboBox\",\"NSComboBoxCell\",\"NSComboButton\",\"NSControl\",\"NSController\",\"NSCursor\",\"NSCustomImageRep\",\"NSCustomTouchBarItem\",\"NSDataAsset\",\"NSDatePicker\",\"NSDatePickerCell\",\"NSDictionaryController\",\"NSDiffableDataSource\",\"NSDirection\",\"NSDockTile\",\"NSDocument\",\"NSDocumentController\",\"NSDocumentScripting\",\"NSDragging\",\"NSDraggingItem\",\"NSDraggingSession\",\"NSDrawer\",\"NSEPSImageRep\",\"NSErrors\",\"NSEvent\",\"NSFilePromiseProvider\",\"NSFilePromiseReceiver\",\"NSFileWrapperExtensions\",\"NSFont\",\"NSFontAssetRequest\",\"NSFontCollection\",\"NSFontDescriptor\",\"NSFontManager\",\"NSFontPanel\",\"NSForm\",\"NSFormCell\",\"NSGestureRecognizer\",\"NSGlyphGenerator\",\"NSGlyphInfo\",\"NSGradient\",\"NSGraphics\",\"NSGraphicsContext\",\"NSGridView\",\"NSGroupTouchBarItem\",\"NSHapticFeedback\",\"NSHelpManager\",\"NSImage\",\"NSImageCell\",\"NSImageRep\",\"NSImageView\",\"NSInputManager\",\"NSInputServer\",\"NSInterfaceStyle\",\"NSItemProvider\",\"NSKeyValueBinding\",\"NSLayoutAnchor\",\"NSLayoutConstraint\",\"NSLayoutGuide\",\"NSLayoutManager\",\"NSLevelIndicator\",\"NSLevelIndicatorCell\",\"NSMagnificationGestureRecognizer\",\"NSMatrix\",\"NSMediaLibraryBrowserController\",\"NSMenu\",\"NSMenuItem\",\"NSMenuItemBadge\",\"NSMenuItemCell\",\"NSMenuToolbarItem\",\"NSMovie\",\"NSNib\",\"NSNibConnector\",\"NSNibControlConnector\",\"NSNibDeclarations\",\"NSNibLoading\",\"NSNibOutletConnector\",\"NSObjectController\",\"NSOpenGL\",\"NSOpenGLLayer\",\"NSOpenGLView\",\"NSOpenPanel\",\"NSOutlineView\",\"NSPDFImageRep\",\"NSPDFInfo\",\"NSPDFPanel\",\"NSPICTImageRep\",\"NSPageController\",\"NSPageLayout\",\"NSPanGestureRecognizer\",\"NSPanel\",\"NSParagraphStyle\",\"NSPasteboard\",\"NSPasteboardItem\",\"NSPathCell\",\"NSPathComponentCell\",\"NSPathControl\",\"NSPathControlItem\",\"NSPersistentDocument\",\"NSPickerTouchBarItem\",\"NSPopUpButton\",\"NSPopUpButtonCell\",\"NSPopover\",\"NSPopoverTouchBarItem\",\"NSPredicateEditor\",\"NSPredicateEditorRowTemplate\",\"NSPressGestureRecognizer\",\"NSPressureConfiguration\",\"NSPreviewRepresentingActivityItem\",\"NSPrintInfo\",\"NSPrintOperation\",\"NSPrintPanel\",\"NSPrinter\",\"NSProgressIndicator\",\"NSResponder\",\"NSRotationGestureRecognizer\",\"NSRuleEditor\",\"NSRulerMarker\",\"NSRulerView\",\"NSRunningApplication\",\"NSSavePanel\",\"NSScreen\",\"NSScrollView\",\"NSScroller\",\"NSScrubber\",\"NSScrubberItemView\",\"NSScrubberLayout\",\"NSSearchField\",\"NSSearchFieldCell\",\"NSSearchToolbarItem\",\"NSSecureTextField\",\"NSSegmentedCell\",\"NSSegmentedControl\",\"NSShadow\",\"NSSharingCollaborationModeRestriction\",\"NSSharingService\",\"NSSharingServicePickerToolbarItem\",\"NSSharingServicePickerTouchBarItem\",\"NSSlider\",\"NSSliderAccessory\",\"NSSliderCell\",\"NSSliderTouchBarItem\",\"NSSound\",\"NSSpeechRecognizer\",\"NSSpeechSynthesizer\",\"NSSpellChecker\",\"NSSpellProtocol\",\"NSSplitView\",\"NSSplitViewController\",\"NSSplitViewItem\",\"NSStackView\",\"NSStatusBar\",\"NSStatusBarButton\",\"NSStatusItem\",\"NSStepper\",\"NSStepperCell\",\"NSStepperTouchBarItem\",\"NSStoryboard\",\"NSStoryboardSegue\",\"NSStringDrawing\",\"NSSwitch\",\"NSTabView\",\"NSTabViewController\",\"NSTabViewItem\",\"NSTableCellView\",\"NSTableColumn\",\"NSTableHeaderCell\",\"NSTableHeaderView\",\"NSTableRowView\",\"NSTableView\",\"NSTableViewDiffableDataSource\",\"NSTableViewRowAction\",\"NSText\",\"NSTextAlternatives\",\"NSTextAttachment\",\"NSTextAttachmentCell\",\"NSTextCheckingClient\",\"NSTextCheckingController\",\"NSTextContainer\",\"NSTextContent\",\"NSTextContentManager\",\"NSTextElement\",\"NSTextField\",\"NSTextFieldCell\",\"NSTextFinder\",\"NSTextInputClient\",\"NSTextInputContext\",\"NSTextInsertionIndicator\",\"NSTextLayoutFragment\",\"NSTextLayoutManager\",\"NSTextLineFragment\",\"NSTextList\",\"NSTextListElement\",\"NSTextRange\",\"NSTextSelection\",\"NSTextSelectionNavigation\",\"NSTextStorage\",\"NSTextStorageScripting\",\"NSTextTable\",\"NSTextView\",\"NSTextViewportLayoutController\",\"NSTintConfiguration\",\"NSTitlebarAccessoryViewController\",\"NSTokenField\",\"NSTokenFieldCell\",\"NSToolbar\",\"NSToolbarItem\",\"NSToolbarItemGroup\",\"NSTouch\",\"NSTouchBar\",\"NSTouchBarItem\",\"NSTrackingArea\",\"NSTrackingSeparatorToolbarItem\",\"NSTreeController\",\"NSTreeNode\",\"NSTypesetter\",\"NSUserActivity\",\"NSUserDefaultsController\",\"NSUserInterfaceCompression\",\"NSUserInterfaceItemIdentification\",\"NSUserInterfaceItemSearching\",\"NSUserInterfaceLayout\",\"NSUserInterfaceValidation\",\"NSView\",\"NSViewController\",\"NSVisualEffectView\",\"NSWindow\",\"NSWindowController\",\"NSWindowRestoration\",\"NSWindowScripting\",\"NSWindowTab\",\"NSWindowTabGroup\",\"NSWorkspace\",\"NSWritingToolsCoordinator\",\"NSWritingToolsCoordinatorAnimationParameters\",\"NSWritingToolsCoordinatorContext\",\"bitflags\",\"block2\",\"libc\",\"objc2-cloud-kit\",\"objc2-core-data\",\"objc2-core-foundation\",\"objc2-core-graphics\",\"objc2-core-image\",\"objc2-quartz-core\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\",\"block2?/gnustep-1-7\",\"objc2-foundation/gnustep-1-7\",\"objc2-core-data?/gnustep-1-7\",\"objc2-quartz-core?/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\",\"block2?/gnustep-1-8\",\"objc2-foundation/gnustep-1-8\",\"objc2-core-data?/gnustep-1-8\",\"objc2-quartz-core?/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\",\"block2?/gnustep-1-9\",\"objc2-foundation/gnustep-1-9\",\"objc2-core-data?/gnustep-1-9\",\"objc2-quartz-core?/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\",\"block2?/gnustep-2-0\",\"objc2-foundation/gnustep-2-0\",\"objc2-core-data?/gnustep-2-0\",\"objc2-quartz-core?/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\",\"block2?/gnustep-2-1\",\"objc2-foundation/gnustep-2-1\",\"objc2-core-data?/gnustep-2-1\",\"objc2-quartz-core?/gnustep-2-1\"],\"libc\":[\"dep:libc\"],\"objc2-cloud-kit\":[\"dep:objc2-cloud-kit\"],\"objc2-core-data\":[\"dep:objc2-core-data\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-graphics\":[\"dep:objc2-core-graphics\"],\"objc2-core-image\":[\"dep:objc2-core-image\"],\"objc2-quartz-core\":[\"dep:objc2-quartz-core\"],\"objc2-uniform-type-identifiers\":[\"dep:objc2-uniform-type-identifiers\"],\"std\":[\"alloc\"]}}", - "objc2-core-foundation_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"dispatch2\",\"optional\":true,\"req\":\">=0.3.0, <0.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"CFArray\":[],\"CFAttributedString\":[],\"CFAvailability\":[],\"CFBag\":[],\"CFBase\":[],\"CFBinaryHeap\":[],\"CFBitVector\":[],\"CFBundle\":[],\"CFByteOrder\":[],\"CFCGTypes\":[],\"CFCalendar\":[\"bitflags\"],\"CFCharacterSet\":[],\"CFData\":[\"bitflags\"],\"CFDate\":[\"bitflags\"],\"CFDateFormatter\":[\"bitflags\"],\"CFDictionary\":[],\"CFError\":[],\"CFFileDescriptor\":[],\"CFFileSecurity\":[\"bitflags\"],\"CFLocale\":[],\"CFMachPort\":[],\"CFMessagePort\":[],\"CFNotificationCenter\":[],\"CFNumber\":[],\"CFNumberFormatter\":[\"bitflags\"],\"CFPlugIn\":[],\"CFPlugInCOM\":[],\"CFPreferences\":[],\"CFPropertyList\":[\"bitflags\"],\"CFRunLoop\":[\"bitflags\"],\"CFSet\":[],\"CFSocket\":[\"bitflags\"],\"CFStream\":[\"bitflags\"],\"CFString\":[\"bitflags\"],\"CFStringEncodingExt\":[],\"CFStringTokenizer\":[\"bitflags\"],\"CFTimeZone\":[],\"CFTree\":[],\"CFURL\":[\"bitflags\"],\"CFURLAccess\":[],\"CFURLEnumerator\":[\"bitflags\"],\"CFUUID\":[],\"CFUserNotification\":[],\"CFUtilities\":[],\"CFXMLNode\":[],\"CFXMLParser\":[\"bitflags\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CFArray\",\"CFAttributedString\",\"CFAvailability\",\"CFBag\",\"CFBase\",\"CFBinaryHeap\",\"CFBitVector\",\"CFBundle\",\"CFByteOrder\",\"CFCGTypes\",\"CFCalendar\",\"CFCharacterSet\",\"CFData\",\"CFDate\",\"CFDateFormatter\",\"CFDictionary\",\"CFError\",\"CFFileDescriptor\",\"CFFileSecurity\",\"CFLocale\",\"CFMachPort\",\"CFMessagePort\",\"CFNotificationCenter\",\"CFNumber\",\"CFNumberFormatter\",\"CFPlugIn\",\"CFPlugInCOM\",\"CFPreferences\",\"CFPropertyList\",\"CFRunLoop\",\"CFSet\",\"CFSocket\",\"CFStream\",\"CFString\",\"CFStringEncodingExt\",\"CFStringTokenizer\",\"CFTimeZone\",\"CFTree\",\"CFURL\",\"CFURLAccess\",\"CFURLEnumerator\",\"CFUUID\",\"CFUserNotification\",\"CFUtilities\",\"CFXMLNode\",\"CFXMLParser\",\"bitflags\",\"block2\",\"dispatch2\",\"libc\",\"objc2\"],\"dispatch2\":[\"dep:dispatch2\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"dispatch2?/objc2\"],\"std\":[\"alloc\"],\"unstable-coerce-pointee\":[]}}", - "objc2-core-graphics_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"dispatch2\",\"optional\":true,\"req\":\">=0.3.0, <0.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"objc2-core-foundation\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"IOSurfaceRef\"],\"name\":\"objc2-io-surface\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(not(target_os = \\\"watchos\\\"))\"},{\"default_features\":false,\"features\":[\"MTLDevice\"],\"name\":\"objc2-metal\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(not(target_os = \\\"watchos\\\"))\"}],\"features\":{\"CGAffineTransform\":[\"objc2-core-foundation/CFCGTypes\"],\"CGBase\":[],\"CGBitmapContext\":[],\"CGColor\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGColorConversionInfo\":[\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFError\"],\"CGColorSpace\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\"],\"CGContext\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGConvertColorDataWithFormat\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGDataConsumer\":[\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFURL\"],\"CGDataProvider\":[\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFURL\"],\"CGDirectDisplay\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGDirectDisplayMetal\":[],\"CGDirectPalette\":[],\"CGDisplayConfiguration\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGDisplayFade\":[],\"CGDisplayStream\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFRunLoop\"],\"CGEXRToneMappingGamma\":[],\"CGError\":[],\"CGEvent\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFMachPort\"],\"CGEventSource\":[\"objc2-core-foundation/CFDate\"],\"CGEventTypes\":[\"bitflags\"],\"CGFont\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFDictionary\"],\"CGFunction\":[\"objc2-core-foundation/CFCGTypes\"],\"CGGeometry\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGGradient\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\"],\"CGITUToneMapping\":[],\"CGImage\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\"],\"CGLayer\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGPDFArray\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFContentStream\":[\"objc2-core-foundation/CFArray\"],\"CGPDFContext\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFURL\"],\"CGPDFDictionary\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFDocument\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFURL\"],\"CGPDFObject\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFOperatorTable\":[],\"CGPDFPage\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFScanner\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFStream\":[\"objc2-core-foundation/CFData\"],\"CGPDFString\":[\"objc2-core-foundation/CFDate\"],\"CGPSConverter\":[\"objc2-core-foundation/CFDictionary\"],\"CGPath\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\"],\"CGPattern\":[\"objc2-core-foundation/CFCGTypes\"],\"CGRemoteOperation\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDate\",\"objc2-core-foundation/CFMachPort\"],\"CGSession\":[\"objc2-core-foundation/CFDictionary\"],\"CGShading\":[\"objc2-core-foundation/CFCGTypes\"],\"CGToneMapping\":[],\"CGWindow\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\"],\"CGWindowLevel\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CGAffineTransform\",\"CGBase\",\"CGBitmapContext\",\"CGColor\",\"CGColorConversionInfo\",\"CGColorSpace\",\"CGContext\",\"CGConvertColorDataWithFormat\",\"CGDataConsumer\",\"CGDataProvider\",\"CGDirectDisplay\",\"CGDirectDisplayMetal\",\"CGDirectPalette\",\"CGDisplayConfiguration\",\"CGDisplayFade\",\"CGDisplayStream\",\"CGEXRToneMappingGamma\",\"CGError\",\"CGEvent\",\"CGEventSource\",\"CGEventTypes\",\"CGFont\",\"CGFunction\",\"CGGeometry\",\"CGGradient\",\"CGITUToneMapping\",\"CGImage\",\"CGLayer\",\"CGPDFArray\",\"CGPDFContentStream\",\"CGPDFContext\",\"CGPDFDictionary\",\"CGPDFDocument\",\"CGPDFObject\",\"CGPDFOperatorTable\",\"CGPDFPage\",\"CGPDFScanner\",\"CGPDFStream\",\"CGPDFString\",\"CGPSConverter\",\"CGPath\",\"CGPattern\",\"CGRemoteOperation\",\"CGSession\",\"CGShading\",\"CGToneMapping\",\"CGWindow\",\"CGWindowLevel\",\"bitflags\",\"block2\",\"dispatch2\",\"libc\",\"objc2\",\"objc2-metal\"],\"dispatch2\":[\"dep:dispatch2\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"dispatch2?/objc2\",\"objc2-core-foundation/objc2\",\"objc2-io-surface?/objc2\"],\"objc2-io-surface\":[\"dep:objc2-io-surface\"],\"objc2-metal\":[\"dep:objc2-metal\"],\"std\":[\"alloc\"]}}", + "objc2-app-kit_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CKContainer\",\"CKRecord\",\"CKShare\",\"CKShareMetadata\"],\"name\":\"objc2-cloud-kit\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"NSAttributeDescription\",\"NSEntityDescription\",\"NSFetchRequest\",\"NSManagedObjectContext\",\"NSManagedObjectModel\",\"NSPersistentStoreRequest\",\"NSPropertyDescription\"],\"name\":\"objc2-core-data\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CFCGTypes\",\"CFDate\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGColor\",\"CGColorSpace\",\"CGContext\",\"CGDirectDisplay\",\"CGEventTypes\",\"CGFont\",\"CGImage\",\"CGPath\",\"objc2\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CIColor\",\"CIContext\",\"CIFilter\",\"CIImage\"],\"name\":\"objc2-core-image\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CTFont\",\"CTFontCollection\",\"CTFontDescriptor\",\"CTGlyphInfo\",\"objc2\"],\"name\":\"objc2-core-text\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CVBase\",\"objc2\"],\"name\":\"objc2-core-video\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGLTypes\"],\"name\":\"objc2-open-gl\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"CADisplayLink\",\"CALayer\",\"CAMediaTiming\",\"CAMediaTimingFunction\",\"CAOpenGLLayer\"],\"name\":\"objc2-quartz-core\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"default_features\":false,\"features\":[\"UTType\"],\"name\":\"objc2-uniform-type-identifiers\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"}],\"features\":{\"AppKitDefines\":[],\"AppKitErrors\":[],\"NSATSTypesetter\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/objc2-core-foundation\"],\"NSAccessibility\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSAccessibilityColor\":[\"objc2-foundation/NSString\"],\"NSAccessibilityConstants\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAccessibilityCustomAction\":[\"objc2-foundation/NSString\"],\"NSAccessibilityCustomRotor\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSAccessibilityElement\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSAccessibilityProtocols\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSActionCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAdaptiveImageGlyph\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAffineTransform\":[\"objc2-foundation/NSAffineTransform\"],\"NSAlert\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"NSAlignmentFeedbackFilter\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/objc2-core-foundation\"],\"NSAnimation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSAnimationContext\":[\"objc2-foundation/NSDate\"],\"NSAppearance\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAppleScriptExtensions\":[\"objc2-foundation/NSAppleScript\",\"objc2-foundation/NSAttributedString\"],\"NSApplication\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSException\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUserActivity\"],\"NSApplicationScripting\":[\"objc2-foundation/NSArray\"],\"NSArrayController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"NSAttributedString\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSBackgroundExtensionView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSBezierPath\":[\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSBitmapImageRep\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSBox\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSBrowser\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSBrowserCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSButton\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSButtonCell\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSButtonTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSCIImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCachedImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCandidateListTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSCell\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSFormatter\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSClickGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSClipView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewCompositionalLayout\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewFlowLayout\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewGridLayout\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewLayout\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSCollectionViewTransitionLayout\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSColor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorList\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSColorPanel\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorPicker\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorPickerTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSColorPicking\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSColorSampler\":[],\"NSColorSpace\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSColorWell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSComboBox\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSComboBoxCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSComboButton\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSFormatter\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSController\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSCursor\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCustomImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSCustomTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDataAsset\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDatePicker\":[\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSTimeZone\",\"objc2-foundation/objc2-core-foundation\"],\"NSDatePickerCell\":[\"bitflags\",\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTimeZone\"],\"NSDictionaryController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDiffableDataSource\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDirection\":[\"bitflags\"],\"NSDockTile\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSDocument\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFilePresenter\",\"objc2-foundation/NSFileVersion\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\"],\"NSDocumentController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSDocumentScripting\":[\"objc2-foundation/NSScriptCommand\",\"objc2-foundation/NSScriptObjectSpecifiers\",\"objc2-foundation/NSScriptStandardSuiteCommands\",\"objc2-foundation/NSString\"],\"NSDragging\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSDraggingItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSDraggingSession\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSDrawer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSEPSImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSErrors\":[\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSString\"],\"NSEvent\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSFilePromiseProvider\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSFilePromiseReceiver\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSFileWrapperExtensions\":[\"objc2-foundation/NSFileWrapper\"],\"NSFont\":[\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSFontAssetRequest\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSProgress\"],\"NSFontCollection\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSFontDescriptor\":[\"bitflags\",\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSFontManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSFontPanel\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSForm\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSFormCell\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGlassEffectView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSGlyphGenerator\":[\"objc2-foundation/NSAttributedString\"],\"NSGlyphInfo\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSGradient\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSGraphics\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGraphicsContext\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSGridView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/objc2-core-foundation\"],\"NSGroupTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSHapticFeedback\":[],\"NSHelpManager\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSImage\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSImageCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSImageRep\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSImageView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSInputManager\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSInputServer\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSInterfaceStyle\":[\"objc2-foundation/NSString\"],\"NSItemBadge\":[\"objc2-foundation/NSString\"],\"NSItemProvider\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSKeyValueBinding\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSLayoutAnchor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSLayoutConstraint\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSLayoutGuide\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSLayoutManager\":[\"bitflags\",\"objc2-foundation/NSAffineTransform\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSLevelIndicator\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSLevelIndicatorCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSMagnificationGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSMatrix\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSMediaLibraryBrowserController\":[\"bitflags\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/objc2-core-foundation\"],\"NSMenu\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSZone\",\"objc2-foundation/objc2-core-foundation\"],\"NSMenuItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSMenuItemBadge\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSMenuItemCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSMenuToolbarItem\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSMovie\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSNib\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSNibConnector\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSNibControlConnector\":[\"objc2-foundation/NSObject\"],\"NSNibDeclarations\":[],\"NSNibLoading\":[],\"NSNibOutletConnector\":[\"objc2-foundation/NSObject\"],\"NSObjectController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"NSOpenGL\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSLock\",\"objc2-foundation/NSObject\"],\"NSOpenGLLayer\":[\"objc2-foundation/NSObject\"],\"NSOpenGLView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSOpenPanel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSOutlineView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPDFImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPDFInfo\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPDFPanel\":[\"bitflags\",\"objc2-foundation/NSString\"],\"NSPICTImageRep\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPageController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPageLayout\":[\"objc2-foundation/NSArray\"],\"NSPanGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPanel\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSParagraphStyle\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPasteboard\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPasteboardItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSPathCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPathComponentCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPathControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSPathControlItem\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPersistentDocument\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFilePresenter\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPickerTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPopUpButton\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPopUpButtonCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPopover\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPopoverTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPredicateEditor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSPredicateEditorRowTemplate\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSComparisonPredicate\",\"objc2-foundation/NSExpression\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSPressGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"NSPressureConfiguration\":[],\"NSPreviewRepresentingActivityItem\":[\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\"],\"NSPrintInfo\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPrintOperation\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSPrintPanel\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSPrinter\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSProgressIndicator\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\",\"objc2-foundation/objc2-core-foundation\"],\"NSResponder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUndoManager\"],\"NSRotationGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSRuleEditor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSRulerMarker\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSRulerView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSRunningApplication\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSavePanel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSScreen\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrollView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScroller\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrubber\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrubberItemView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSScrubberLayout\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/objc2-core-foundation\"],\"NSSearchField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSearchFieldCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSearchToolbarItem\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSecureTextField\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSegmentedCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSegmentedControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSShadow\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSSharingCollaborationModeRestriction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSharingService\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSSharingServicePickerToolbarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSharingServicePickerTouchBarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSlider\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSliderAccessory\":[\"objc2-foundation/NSObject\"],\"NSSliderCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSliderTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSSound\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSpeechRecognizer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSSpeechSynthesizer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSSpellChecker\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSOrthography\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\",\"objc2-foundation/objc2-core-foundation\"],\"NSSpellProtocol\":[],\"NSSplitView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSplitViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSplitViewItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"NSSplitViewItemAccessoryViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSStackView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSStatusBar\":[],\"NSStatusBarButton\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSStatusItem\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSStepper\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSStepperCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSStepperTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSFormatter\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSStoryboard\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSString\"],\"NSStoryboardSegue\":[\"objc2-foundation/NSString\"],\"NSStringDrawing\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSSwitch\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTabView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTabViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTabViewItem\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableCellView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableColumn\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"NSTableHeaderCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableHeaderView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableRowView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSEnumerator\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/objc2-core-foundation\"],\"NSTableViewDiffableDataSource\":[],\"NSTableViewRowAction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSText\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextAlternatives\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextAttachment\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextAttachmentCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextCheckingClient\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextCheckingController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\"],\"NSTextContainer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextContent\":[\"objc2-foundation/NSString\"],\"NSTextContentManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\"],\"NSTextField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextFieldCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextFinder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextInputClient\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextInputContext\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"NSTextInsertionIndicator\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextLayoutFragment\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\"],\"NSTextLayoutManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"NSTextLineFragment\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextList\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextListElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSTextRange\":[\"objc2-foundation/NSObjCRuntime\"],\"NSTextSelection\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextSelectionNavigation\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSTextStorage\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextStorageScripting\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\"],\"NSTextTable\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOrthography\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTextCheckingResult\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSTextViewportLayoutController\":[],\"NSTintConfiguration\":[\"objc2-foundation/NSObject\"],\"NSTintProminence\":[],\"NSTitlebarAccessoryViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTokenField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSTokenFieldCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSToolbar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSToolbarItem\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSToolbarItemGroup\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTouch\":[\"bitflags\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTouchBar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSTouchBarItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTrackingArea\":[\"bitflags\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSTrackingSeparatorToolbarItem\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTreeController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"NSTreeNode\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSSortDescriptor\"],\"NSTypesetter\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSUserActivity\":[\"objc2-foundation/NSString\",\"objc2-foundation/NSUserActivity\"],\"NSUserDefaultsController\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUserDefaults\"],\"NSUserInterfaceCompression\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSUserInterfaceItemIdentification\":[\"objc2-foundation/NSString\"],\"NSUserInterfaceItemSearching\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSUserInterfaceLayout\":[],\"NSUserInterfaceValidation\":[],\"NSView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSViewController\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSExtensionContext\",\"objc2-foundation/NSExtensionRequestHandling\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/objc2-core-foundation\"],\"NSViewLayoutRegion\":[\"objc2-foundation/NSGeometry\",\"objc2-foundation/objc2-core-foundation\"],\"NSVisualEffectView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSObject\",\"objc2-foundation/objc2-core-foundation\"],\"NSWindow\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSWindowController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSWindowRestoration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"NSWindowScripting\":[\"objc2-foundation/NSScriptCommand\",\"objc2-foundation/NSScriptStandardSuiteCommands\"],\"NSWindowTab\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"NSWindowTabGroup\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSWorkspace\":[\"bitflags\",\"objc2-foundation/NSAppleEventDescriptor\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileManager\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSWritingToolsCoordinator\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSGeometry\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSUUID\",\"objc2-foundation/NSValue\",\"objc2-foundation/objc2-core-foundation\"],\"NSWritingToolsCoordinatorAnimationParameters\":[],\"NSWritingToolsCoordinatorContext\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSUUID\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"AppKitDefines\",\"AppKitErrors\",\"NSATSTypesetter\",\"NSAccessibility\",\"NSAccessibilityColor\",\"NSAccessibilityConstants\",\"NSAccessibilityCustomAction\",\"NSAccessibilityCustomRotor\",\"NSAccessibilityElement\",\"NSAccessibilityProtocols\",\"NSActionCell\",\"NSAdaptiveImageGlyph\",\"NSAffineTransform\",\"NSAlert\",\"NSAlignmentFeedbackFilter\",\"NSAnimation\",\"NSAnimationContext\",\"NSAppearance\",\"NSAppleScriptExtensions\",\"NSApplication\",\"NSApplicationScripting\",\"NSArrayController\",\"NSAttributedString\",\"NSBackgroundExtensionView\",\"NSBezierPath\",\"NSBitmapImageRep\",\"NSBox\",\"NSBrowser\",\"NSBrowserCell\",\"NSButton\",\"NSButtonCell\",\"NSButtonTouchBarItem\",\"NSCIImageRep\",\"NSCachedImageRep\",\"NSCandidateListTouchBarItem\",\"NSCell\",\"NSClickGestureRecognizer\",\"NSClipView\",\"NSCollectionView\",\"NSCollectionViewCompositionalLayout\",\"NSCollectionViewFlowLayout\",\"NSCollectionViewGridLayout\",\"NSCollectionViewLayout\",\"NSCollectionViewTransitionLayout\",\"NSColor\",\"NSColorList\",\"NSColorPanel\",\"NSColorPicker\",\"NSColorPickerTouchBarItem\",\"NSColorPicking\",\"NSColorSampler\",\"NSColorSpace\",\"NSColorWell\",\"NSComboBox\",\"NSComboBoxCell\",\"NSComboButton\",\"NSControl\",\"NSController\",\"NSCursor\",\"NSCustomImageRep\",\"NSCustomTouchBarItem\",\"NSDataAsset\",\"NSDatePicker\",\"NSDatePickerCell\",\"NSDictionaryController\",\"NSDiffableDataSource\",\"NSDirection\",\"NSDockTile\",\"NSDocument\",\"NSDocumentController\",\"NSDocumentScripting\",\"NSDragging\",\"NSDraggingItem\",\"NSDraggingSession\",\"NSDrawer\",\"NSEPSImageRep\",\"NSErrors\",\"NSEvent\",\"NSFilePromiseProvider\",\"NSFilePromiseReceiver\",\"NSFileWrapperExtensions\",\"NSFont\",\"NSFontAssetRequest\",\"NSFontCollection\",\"NSFontDescriptor\",\"NSFontManager\",\"NSFontPanel\",\"NSForm\",\"NSFormCell\",\"NSGestureRecognizer\",\"NSGlassEffectView\",\"NSGlyphGenerator\",\"NSGlyphInfo\",\"NSGradient\",\"NSGraphics\",\"NSGraphicsContext\",\"NSGridView\",\"NSGroupTouchBarItem\",\"NSHapticFeedback\",\"NSHelpManager\",\"NSImage\",\"NSImageCell\",\"NSImageRep\",\"NSImageView\",\"NSInputManager\",\"NSInputServer\",\"NSInterfaceStyle\",\"NSItemBadge\",\"NSItemProvider\",\"NSKeyValueBinding\",\"NSLayoutAnchor\",\"NSLayoutConstraint\",\"NSLayoutGuide\",\"NSLayoutManager\",\"NSLevelIndicator\",\"NSLevelIndicatorCell\",\"NSMagnificationGestureRecognizer\",\"NSMatrix\",\"NSMediaLibraryBrowserController\",\"NSMenu\",\"NSMenuItem\",\"NSMenuItemBadge\",\"NSMenuItemCell\",\"NSMenuToolbarItem\",\"NSMovie\",\"NSNib\",\"NSNibConnector\",\"NSNibControlConnector\",\"NSNibDeclarations\",\"NSNibLoading\",\"NSNibOutletConnector\",\"NSObjectController\",\"NSOpenGL\",\"NSOpenGLLayer\",\"NSOpenGLView\",\"NSOpenPanel\",\"NSOutlineView\",\"NSPDFImageRep\",\"NSPDFInfo\",\"NSPDFPanel\",\"NSPICTImageRep\",\"NSPageController\",\"NSPageLayout\",\"NSPanGestureRecognizer\",\"NSPanel\",\"NSParagraphStyle\",\"NSPasteboard\",\"NSPasteboardItem\",\"NSPathCell\",\"NSPathComponentCell\",\"NSPathControl\",\"NSPathControlItem\",\"NSPersistentDocument\",\"NSPickerTouchBarItem\",\"NSPopUpButton\",\"NSPopUpButtonCell\",\"NSPopover\",\"NSPopoverTouchBarItem\",\"NSPredicateEditor\",\"NSPredicateEditorRowTemplate\",\"NSPressGestureRecognizer\",\"NSPressureConfiguration\",\"NSPreviewRepresentingActivityItem\",\"NSPrintInfo\",\"NSPrintOperation\",\"NSPrintPanel\",\"NSPrinter\",\"NSProgressIndicator\",\"NSResponder\",\"NSRotationGestureRecognizer\",\"NSRuleEditor\",\"NSRulerMarker\",\"NSRulerView\",\"NSRunningApplication\",\"NSSavePanel\",\"NSScreen\",\"NSScrollView\",\"NSScroller\",\"NSScrubber\",\"NSScrubberItemView\",\"NSScrubberLayout\",\"NSSearchField\",\"NSSearchFieldCell\",\"NSSearchToolbarItem\",\"NSSecureTextField\",\"NSSegmentedCell\",\"NSSegmentedControl\",\"NSShadow\",\"NSSharingCollaborationModeRestriction\",\"NSSharingService\",\"NSSharingServicePickerToolbarItem\",\"NSSharingServicePickerTouchBarItem\",\"NSSlider\",\"NSSliderAccessory\",\"NSSliderCell\",\"NSSliderTouchBarItem\",\"NSSound\",\"NSSpeechRecognizer\",\"NSSpeechSynthesizer\",\"NSSpellChecker\",\"NSSpellProtocol\",\"NSSplitView\",\"NSSplitViewController\",\"NSSplitViewItem\",\"NSSplitViewItemAccessoryViewController\",\"NSStackView\",\"NSStatusBar\",\"NSStatusBarButton\",\"NSStatusItem\",\"NSStepper\",\"NSStepperCell\",\"NSStepperTouchBarItem\",\"NSStoryboard\",\"NSStoryboardSegue\",\"NSStringDrawing\",\"NSSwitch\",\"NSTabView\",\"NSTabViewController\",\"NSTabViewItem\",\"NSTableCellView\",\"NSTableColumn\",\"NSTableHeaderCell\",\"NSTableHeaderView\",\"NSTableRowView\",\"NSTableView\",\"NSTableViewDiffableDataSource\",\"NSTableViewRowAction\",\"NSText\",\"NSTextAlternatives\",\"NSTextAttachment\",\"NSTextAttachmentCell\",\"NSTextCheckingClient\",\"NSTextCheckingController\",\"NSTextContainer\",\"NSTextContent\",\"NSTextContentManager\",\"NSTextElement\",\"NSTextField\",\"NSTextFieldCell\",\"NSTextFinder\",\"NSTextInputClient\",\"NSTextInputContext\",\"NSTextInsertionIndicator\",\"NSTextLayoutFragment\",\"NSTextLayoutManager\",\"NSTextLineFragment\",\"NSTextList\",\"NSTextListElement\",\"NSTextRange\",\"NSTextSelection\",\"NSTextSelectionNavigation\",\"NSTextStorage\",\"NSTextStorageScripting\",\"NSTextTable\",\"NSTextView\",\"NSTextViewportLayoutController\",\"NSTintConfiguration\",\"NSTintProminence\",\"NSTitlebarAccessoryViewController\",\"NSTokenField\",\"NSTokenFieldCell\",\"NSToolbar\",\"NSToolbarItem\",\"NSToolbarItemGroup\",\"NSTouch\",\"NSTouchBar\",\"NSTouchBarItem\",\"NSTrackingArea\",\"NSTrackingSeparatorToolbarItem\",\"NSTreeController\",\"NSTreeNode\",\"NSTypesetter\",\"NSUserActivity\",\"NSUserDefaultsController\",\"NSUserInterfaceCompression\",\"NSUserInterfaceItemIdentification\",\"NSUserInterfaceItemSearching\",\"NSUserInterfaceLayout\",\"NSUserInterfaceValidation\",\"NSView\",\"NSViewController\",\"NSViewLayoutRegion\",\"NSVisualEffectView\",\"NSWindow\",\"NSWindowController\",\"NSWindowRestoration\",\"NSWindowScripting\",\"NSWindowTab\",\"NSWindowTabGroup\",\"NSWorkspace\",\"NSWritingToolsCoordinator\",\"NSWritingToolsCoordinatorAnimationParameters\",\"NSWritingToolsCoordinatorContext\",\"bitflags\",\"block2\",\"libc\",\"objc2-cloud-kit\",\"objc2-core-data\",\"objc2-core-foundation\",\"objc2-core-graphics\",\"objc2-core-image\",\"objc2-core-text\",\"objc2-core-video\",\"objc2-quartz-core\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\",\"block2?/gnustep-1-7\",\"objc2-foundation/gnustep-1-7\",\"objc2-core-data?/gnustep-1-7\",\"objc2-quartz-core?/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\",\"block2?/gnustep-1-8\",\"objc2-foundation/gnustep-1-8\",\"objc2-core-data?/gnustep-1-8\",\"objc2-quartz-core?/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\",\"block2?/gnustep-1-9\",\"objc2-foundation/gnustep-1-9\",\"objc2-core-data?/gnustep-1-9\",\"objc2-quartz-core?/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\",\"block2?/gnustep-2-0\",\"objc2-foundation/gnustep-2-0\",\"objc2-core-data?/gnustep-2-0\",\"objc2-quartz-core?/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\",\"block2?/gnustep-2-1\",\"objc2-foundation/gnustep-2-1\",\"objc2-core-data?/gnustep-2-1\",\"objc2-quartz-core?/gnustep-2-1\"],\"libc\":[\"dep:libc\"],\"objc2-cloud-kit\":[\"dep:objc2-cloud-kit\"],\"objc2-core-data\":[\"dep:objc2-core-data\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-graphics\":[\"dep:objc2-core-graphics\"],\"objc2-core-image\":[\"dep:objc2-core-image\"],\"objc2-core-text\":[\"dep:objc2-core-text\"],\"objc2-core-video\":[\"dep:objc2-core-video\"],\"objc2-open-gl\":[\"dep:objc2-open-gl\"],\"objc2-quartz-core\":[\"dep:objc2-quartz-core\"],\"objc2-uniform-type-identifiers\":[\"dep:objc2-uniform-type-identifiers\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-cloud-kit_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CNContact\"],\"name\":\"objc2-contacts\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"features\":[\"CLLocation\"],\"name\":\"objc2-core-location\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"}],\"features\":{\"CKAcceptSharesOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKAllowedSharingOptions\":[\"bitflags\",\"objc2-foundation/NSObject\"],\"CKAsset\":[\"objc2-foundation/NSURL\"],\"CKContainer\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CKDatabase\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKDatabaseOperation\":[\"objc2-foundation/NSOperation\"],\"CKDefines\":[],\"CKDiscoverAllUserIdentitiesOperation\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKDiscoverUserIdentitiesOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKError\":[\"objc2-foundation/NSString\"],\"CKFetchDatabaseChangesOperation\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKFetchNotificationChangesOperation\":[\"objc2-foundation/NSOperation\"],\"CKFetchRecordChangesOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKFetchRecordZoneChangesOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKFetchRecordZonesOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKFetchRecordsOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKFetchShareMetadataOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CKFetchShareParticipantsOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKFetchSubscriptionsOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKFetchWebAuthTokenOperation\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKLocationSortDescriptor\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\",\"objc2-foundation/block2\"],\"CKMarkNotificationsReadOperation\":[\"objc2-foundation/NSOperation\"],\"CKModifyBadgeOperation\":[\"objc2-foundation/NSOperation\"],\"CKModifyRecordZonesOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKModifyRecordsOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\"],\"CKModifySubscriptionsOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKNotification\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"CKOperation\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKOperationGroup\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CKQuery\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\"],\"CKQueryOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"CKRecord\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"CKRecordID\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CKRecordZone\":[\"bitflags\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CKRecordZoneID\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CKReference\":[\"objc2-foundation/NSObject\"],\"CKServerChangeToken\":[\"objc2-foundation/NSObject\"],\"CKShare\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CKShareAccessRequester\":[\"objc2-foundation/NSObject\"],\"CKShareBlockedIdentity\":[\"objc2-foundation/NSObject\"],\"CKShareMetadata\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CKShareParticipant\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CKShareRequestAccessOperation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSURL\"],\"CKSubscription\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"CKSyncEngine\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\"],\"CKSyncEngineConfiguration\":[\"objc2-foundation/NSString\"],\"CKSyncEngineEvent\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"CKSyncEngineRecordZoneChangeBatch\":[\"objc2-foundation/NSArray\"],\"CKSyncEngineState\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"CKSystemSharingUIObserver\":[\"objc2-foundation/NSError\"],\"CKUserIdentity\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPersonNameComponents\",\"objc2-foundation/NSString\"],\"CKUserIdentityLookupInfo\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSItemProvider_CKSharingSupport\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSItemProvider\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CKAcceptSharesOperation\",\"CKAllowedSharingOptions\",\"CKAsset\",\"CKContainer\",\"CKDatabase\",\"CKDatabaseOperation\",\"CKDefines\",\"CKDiscoverAllUserIdentitiesOperation\",\"CKDiscoverUserIdentitiesOperation\",\"CKError\",\"CKFetchDatabaseChangesOperation\",\"CKFetchNotificationChangesOperation\",\"CKFetchRecordChangesOperation\",\"CKFetchRecordZoneChangesOperation\",\"CKFetchRecordZonesOperation\",\"CKFetchRecordsOperation\",\"CKFetchShareMetadataOperation\",\"CKFetchShareParticipantsOperation\",\"CKFetchSubscriptionsOperation\",\"CKFetchWebAuthTokenOperation\",\"CKLocationSortDescriptor\",\"CKMarkNotificationsReadOperation\",\"CKModifyBadgeOperation\",\"CKModifyRecordZonesOperation\",\"CKModifyRecordsOperation\",\"CKModifySubscriptionsOperation\",\"CKNotification\",\"CKOperation\",\"CKOperationGroup\",\"CKQuery\",\"CKQueryOperation\",\"CKRecord\",\"CKRecordID\",\"CKRecordZone\",\"CKRecordZoneID\",\"CKReference\",\"CKServerChangeToken\",\"CKShare\",\"CKShareAccessRequester\",\"CKShareBlockedIdentity\",\"CKShareMetadata\",\"CKShareParticipant\",\"CKShareRequestAccessOperation\",\"CKSubscription\",\"CKSyncEngine\",\"CKSyncEngineConfiguration\",\"CKSyncEngineEvent\",\"CKSyncEngineRecordZoneChangeBatch\",\"CKSyncEngineState\",\"CKSystemSharingUIObserver\",\"CKUserIdentity\",\"CKUserIdentityLookupInfo\",\"NSItemProvider_CKSharingSupport\",\"bitflags\",\"block2\",\"objc2-contacts\",\"objc2-core-location\"],\"objc2-contacts\":[\"dep:objc2-contacts\"],\"objc2-core-location\":[\"dep:objc2-core-location\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-core-data_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CKContainer\",\"CKDatabase\",\"CKRecord\",\"CKRecordID\",\"CKRecordZoneID\",\"CKShare\",\"CKShareMetadata\",\"CKShareParticipant\",\"CKUserIdentityLookupInfo\"],\"name\":\"objc2-cloud-kit\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CSSearchableIndex\",\"CSSearchableItemAttributeSet\"],\"name\":\"objc2-core-spotlight\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(any(target_os = \\\"tvos\\\", target_os = \\\"watchos\\\")))\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"}],\"features\":{\"CloudKit\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\"],\"CoreDataDefines\":[],\"CoreDataErrors\":[\"objc2-foundation/NSString\"],\"NSAtomicStore\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSAtomicStoreCacheNode\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSAttributeDescription\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSBatchDeleteRequest\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"NSBatchInsertRequest\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSBatchUpdateRequest\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"NSCompositeAttributeDescription\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"NSCoreDataCoreSpotlightDelegate\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"NSCustomMigrationStage\":[\"objc2-foundation/NSError\"],\"NSDerivedAttributeDescription\":[\"objc2-foundation/NSExpression\",\"objc2-foundation/NSObject\"],\"NSEntityDescription\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSEnumerator\",\"objc2-foundation/NSExpression\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSEntityMapping\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSExpression\",\"objc2-foundation/NSString\"],\"NSEntityMigrationPolicy\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"NSExpressionDescription\":[\"objc2-foundation/NSExpression\",\"objc2-foundation/NSObject\"],\"NSFetchIndexDescription\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"NSFetchIndexElementDescription\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSFetchRequest\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSSortDescriptor\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"NSFetchRequestExpression\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSExpression\",\"objc2-foundation/NSObject\"],\"NSFetchedPropertyDescription\":[\"objc2-foundation/NSObject\"],\"NSFetchedResultsController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSOrderedCollectionDifference\",\"objc2-foundation/NSString\",\"NSFetchRequest\"],\"NSIncrementalStore\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSIncrementalStoreNode\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSLightweightMigrationStage\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSManagedObject\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSKeyValueObserving\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"NSManagedObjectContext\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSLock\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUndoManager\"],\"NSManagedObjectID\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSURL\"],\"NSManagedObjectModel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSEnumerator\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSManagedObjectModelReference\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSMappingModel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSMergePolicy\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"NSMigrationManager\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSMigrationStage\":[\"objc2-foundation/NSString\"],\"NSPersistentCloudKitContainer\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"NSPersistentCloudKitContainerEvent\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUUID\"],\"NSPersistentCloudKitContainerEventRequest\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"NSPersistentCloudKitContainerOptions\":[\"objc2-foundation/NSString\"],\"NSPersistentContainer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPersistentHistoryChange\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\"],\"NSPersistentHistoryChangeRequest\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"NSPersistentHistoryToken\":[\"objc2-foundation/NSObject\"],\"NSPersistentHistoryTransaction\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSPersistentStore\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPersistentStoreCoordinator\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSLock\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSValue\"],\"NSPersistentStoreDescription\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSPersistentStoreRequest\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"NSPersistentStoreResult\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSProgress\",\"NSFetchRequest\"],\"NSPropertyDescription\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"NSPropertyMapping\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSExpression\",\"objc2-foundation/NSString\"],\"NSQueryGenerationToken\":[\"objc2-foundation/NSObject\"],\"NSRelationshipDescription\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\"],\"NSSaveChangesRequest\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\"],\"NSStagedMigrationManager\":[\"objc2-foundation/NSArray\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CloudKit\",\"CoreDataDefines\",\"CoreDataErrors\",\"NSAtomicStore\",\"NSAtomicStoreCacheNode\",\"NSAttributeDescription\",\"NSBatchDeleteRequest\",\"NSBatchInsertRequest\",\"NSBatchUpdateRequest\",\"NSCompositeAttributeDescription\",\"NSCoreDataCoreSpotlightDelegate\",\"NSCustomMigrationStage\",\"NSDerivedAttributeDescription\",\"NSEntityDescription\",\"NSEntityMapping\",\"NSEntityMigrationPolicy\",\"NSExpressionDescription\",\"NSFetchIndexDescription\",\"NSFetchIndexElementDescription\",\"NSFetchRequest\",\"NSFetchRequestExpression\",\"NSFetchedPropertyDescription\",\"NSFetchedResultsController\",\"NSIncrementalStore\",\"NSIncrementalStoreNode\",\"NSLightweightMigrationStage\",\"NSManagedObject\",\"NSManagedObjectContext\",\"NSManagedObjectID\",\"NSManagedObjectModel\",\"NSManagedObjectModelReference\",\"NSMappingModel\",\"NSMergePolicy\",\"NSMigrationManager\",\"NSMigrationStage\",\"NSPersistentCloudKitContainer\",\"NSPersistentCloudKitContainerEvent\",\"NSPersistentCloudKitContainerEventRequest\",\"NSPersistentCloudKitContainerOptions\",\"NSPersistentContainer\",\"NSPersistentHistoryChange\",\"NSPersistentHistoryChangeRequest\",\"NSPersistentHistoryToken\",\"NSPersistentHistoryTransaction\",\"NSPersistentStore\",\"NSPersistentStoreCoordinator\",\"NSPersistentStoreDescription\",\"NSPersistentStoreRequest\",\"NSPersistentStoreResult\",\"NSPropertyDescription\",\"NSPropertyMapping\",\"NSQueryGenerationToken\",\"NSRelationshipDescription\",\"NSSaveChangesRequest\",\"NSStagedMigrationManager\",\"bitflags\",\"block2\",\"objc2-cloud-kit\"],\"gnustep-1-7\":[],\"gnustep-1-8\":[],\"gnustep-1-9\":[],\"gnustep-2-0\":[],\"gnustep-2-1\":[],\"objc2-cloud-kit\":[\"dep:objc2-cloud-kit\"],\"objc2-core-spotlight\":[\"dep:objc2-core-spotlight\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-core-foundation_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"dispatch2\",\"optional\":true,\"req\":\">=0.3.0, <0.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.2, <0.8.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"CFArray\":[],\"CFAttributedString\":[],\"CFAvailability\":[],\"CFBag\":[],\"CFBase\":[],\"CFBinaryHeap\":[],\"CFBitVector\":[],\"CFBundle\":[],\"CFByteOrder\":[],\"CFCGTypes\":[],\"CFCalendar\":[\"bitflags\"],\"CFCharacterSet\":[],\"CFData\":[\"bitflags\"],\"CFDate\":[\"bitflags\"],\"CFDateFormatter\":[\"bitflags\"],\"CFDictionary\":[],\"CFError\":[],\"CFFileDescriptor\":[],\"CFFileSecurity\":[\"bitflags\"],\"CFLocale\":[],\"CFMachPort\":[],\"CFMessagePort\":[],\"CFNotificationCenter\":[],\"CFNumber\":[],\"CFNumberFormatter\":[\"bitflags\"],\"CFPlugIn\":[],\"CFPlugInCOM\":[],\"CFPreferences\":[],\"CFPropertyList\":[\"bitflags\"],\"CFRunLoop\":[\"bitflags\"],\"CFSet\":[],\"CFSocket\":[\"bitflags\"],\"CFStream\":[\"bitflags\"],\"CFString\":[\"bitflags\"],\"CFStringEncodingExt\":[],\"CFStringTokenizer\":[\"bitflags\"],\"CFTimeZone\":[],\"CFTree\":[],\"CFURL\":[\"bitflags\"],\"CFURLAccess\":[],\"CFURLEnumerator\":[\"bitflags\"],\"CFUUID\":[],\"CFUserNotification\":[],\"CFUtilities\":[],\"CFXMLNode\":[],\"CFXMLParser\":[\"bitflags\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CFArray\",\"CFAttributedString\",\"CFAvailability\",\"CFBag\",\"CFBinaryHeap\",\"CFBitVector\",\"CFBundle\",\"CFByteOrder\",\"CFCGTypes\",\"CFCalendar\",\"CFCharacterSet\",\"CFData\",\"CFDate\",\"CFDateFormatter\",\"CFDictionary\",\"CFError\",\"CFFileDescriptor\",\"CFFileSecurity\",\"CFLocale\",\"CFMachPort\",\"CFMessagePort\",\"CFNotificationCenter\",\"CFNumber\",\"CFNumberFormatter\",\"CFPlugIn\",\"CFPlugInCOM\",\"CFPreferences\",\"CFPropertyList\",\"CFRunLoop\",\"CFSet\",\"CFSocket\",\"CFStream\",\"CFString\",\"CFStringEncodingExt\",\"CFStringTokenizer\",\"CFTimeZone\",\"CFTree\",\"CFURL\",\"CFURLAccess\",\"CFURLEnumerator\",\"CFUUID\",\"CFUserNotification\",\"CFUtilities\",\"CFXMLNode\",\"CFXMLParser\",\"bitflags\",\"block2\",\"dispatch2\",\"libc\",\"objc2\"],\"dispatch2\":[\"dep:dispatch2\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"dispatch2?/objc2\"],\"std\":[\"alloc\"],\"unstable-coerce-pointee\":[],\"unstable-darwin-objc\":[]}}", + "objc2-core-graphics_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"dispatch2\",\"optional\":true,\"req\":\">=0.3.0, <0.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"name\":\"objc2-core-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"IOSurfaceRef\"],\"name\":\"objc2-io-surface\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(target_os = \\\"watchos\\\"))\"},{\"default_features\":false,\"features\":[\"MTLDevice\"],\"name\":\"objc2-metal\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(target_os = \\\"watchos\\\"))\"}],\"features\":{\"CGAffineTransform\":[\"objc2-core-foundation/CFCGTypes\"],\"CGBase\":[],\"CGBitmapContext\":[\"bitflags\",\"objc2-core-foundation/CFByteOrder\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFError\"],\"CGColor\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGColorConversionInfo\":[\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFError\"],\"CGColorSpace\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\"],\"CGContext\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGConvertColorDataWithFormat\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGDataConsumer\":[\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFURL\"],\"CGDataProvider\":[\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFURL\"],\"CGDirectDisplay\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGDirectDisplayMetal\":[],\"CGDirectPalette\":[],\"CGDisplayConfiguration\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGDisplayFade\":[],\"CGDisplayStream\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFRunLoop\"],\"CGEXRToneMappingGamma\":[\"objc2-core-foundation/CFDictionary\"],\"CGError\":[],\"CGEvent\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFMachPort\"],\"CGEventSource\":[\"objc2-core-foundation/CFDate\"],\"CGEventTypes\":[\"bitflags\"],\"CGFont\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFDictionary\"],\"CGFunction\":[\"objc2-core-foundation/CFCGTypes\"],\"CGGeometry\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGGradient\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\"],\"CGITUToneMapping\":[],\"CGImage\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\"],\"CGLayer\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CGPDFArray\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFContentStream\":[\"objc2-core-foundation/CFArray\"],\"CGPDFContext\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFURL\"],\"CGPDFDictionary\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFDocument\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFURL\"],\"CGPDFObject\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFOperatorTable\":[],\"CGPDFPage\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFScanner\":[\"objc2-core-foundation/CFCGTypes\"],\"CGPDFStream\":[\"objc2-core-foundation/CFData\"],\"CGPDFString\":[\"objc2-core-foundation/CFDate\"],\"CGPSConverter\":[\"objc2-core-foundation/CFDictionary\"],\"CGPath\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\"],\"CGPattern\":[\"objc2-core-foundation/CFCGTypes\"],\"CGRemoteOperation\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDate\",\"objc2-core-foundation/CFMachPort\"],\"CGRenderingBufferProvider\":[\"objc2-core-foundation/CFData\"],\"CGSession\":[\"objc2-core-foundation/CFDictionary\"],\"CGShading\":[\"objc2-core-foundation/CFCGTypes\"],\"CGToneMapping\":[\"objc2-core-foundation/CFDictionary\"],\"CGWindow\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\"],\"CGWindowLevel\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CGAffineTransform\",\"CGBase\",\"CGBitmapContext\",\"CGColor\",\"CGColorConversionInfo\",\"CGColorSpace\",\"CGContext\",\"CGConvertColorDataWithFormat\",\"CGDataConsumer\",\"CGDataProvider\",\"CGDirectDisplay\",\"CGDirectDisplayMetal\",\"CGDirectPalette\",\"CGDisplayConfiguration\",\"CGDisplayFade\",\"CGDisplayStream\",\"CGEXRToneMappingGamma\",\"CGError\",\"CGEvent\",\"CGEventSource\",\"CGEventTypes\",\"CGFont\",\"CGFunction\",\"CGGeometry\",\"CGGradient\",\"CGITUToneMapping\",\"CGImage\",\"CGLayer\",\"CGPDFArray\",\"CGPDFContentStream\",\"CGPDFContext\",\"CGPDFDictionary\",\"CGPDFDocument\",\"CGPDFObject\",\"CGPDFOperatorTable\",\"CGPDFPage\",\"CGPDFScanner\",\"CGPDFStream\",\"CGPDFString\",\"CGPSConverter\",\"CGPath\",\"CGPattern\",\"CGRemoteOperation\",\"CGRenderingBufferProvider\",\"CGSession\",\"CGShading\",\"CGToneMapping\",\"CGWindow\",\"CGWindowLevel\",\"bitflags\",\"block2\",\"dispatch2\",\"libc\",\"objc2\",\"objc2-metal\"],\"dispatch2\":[\"dep:dispatch2\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"dispatch2?/objc2\",\"objc2-core-foundation/objc2\",\"objc2-io-surface?/objc2\"],\"objc2-io-surface\":[\"dep:objc2-io-surface\"],\"objc2-metal\":[\"dep:objc2-metal\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-core-image_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CFCGTypes\",\"CFDictionary\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGColor\",\"CGColorSpace\",\"CGContext\",\"CGImage\",\"CGLayer\",\"objc2\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"MLModel\"],\"name\":\"objc2-core-ml\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CVBuffer\",\"CVImageBuffer\",\"CVPixelBuffer\",\"objc2\"],\"name\":\"objc2-core-video\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGImageProperties\",\"CGImageSource\",\"objc2\"],\"name\":\"objc2-image-io\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"IOSurfaceRef\",\"objc2\"],\"name\":\"objc2-io-surface\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"MTLAllocation\",\"MTLCommandBuffer\",\"MTLCommandQueue\",\"MTLDevice\",\"MTLPixelFormat\",\"MTLResource\",\"MTLTexture\"],\"name\":\"objc2-metal\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGLTypes\"],\"name\":\"objc2-open-gl\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_os = \\\"macos\\\")\"}],\"features\":{\"CIBarcodeDescriptor\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSUserActivity\"],\"CIColor\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CIContext\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CIDetector\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"CIFeature\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CIFilter\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CIFilterBuiltins\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSString\"],\"CIFilterConstructor\":[\"objc2-foundation/NSString\"],\"CIFilterGenerator\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CIFilterShape\":[\"objc2-foundation/NSObject\"],\"CIImage\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CIImageAccumulator\":[],\"CIImageProcessor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"CIImageProvider\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"CIKernel\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"CIKernelMetalLib\":[],\"CIPlugIn\":[\"objc2-foundation/NSURL\"],\"CIPlugInInterface\":[],\"CIRAWFilter\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CIRAWFilter_Deprecated\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"CIRenderDestination\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSError\",\"objc2-foundation/NSURL\"],\"CISampler\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CIVector\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CoreImageDefines\":[],\"alloc\":[],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CIBarcodeDescriptor\",\"CIColor\",\"CIContext\",\"CIDetector\",\"CIFeature\",\"CIFilter\",\"CIFilterBuiltins\",\"CIFilterConstructor\",\"CIFilterGenerator\",\"CIFilterShape\",\"CIImage\",\"CIImageAccumulator\",\"CIImageProcessor\",\"CIImageProvider\",\"CIKernel\",\"CIKernelMetalLib\",\"CIPlugIn\",\"CIPlugInInterface\",\"CIRAWFilter\",\"CIRAWFilter_Deprecated\",\"CIRenderDestination\",\"CISampler\",\"CIVector\",\"CoreImageDefines\",\"block2\",\"objc2-core-foundation\",\"objc2-core-graphics\",\"objc2-core-video\",\"objc2-image-io\",\"objc2-metal\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\",\"block2?/gnustep-1-7\",\"objc2-foundation/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\",\"block2?/gnustep-1-8\",\"objc2-foundation/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\",\"block2?/gnustep-1-9\",\"objc2-foundation/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\",\"block2?/gnustep-2-0\",\"objc2-foundation/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\",\"block2?/gnustep-2-1\",\"objc2-foundation/gnustep-2-1\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-graphics\":[\"dep:objc2-core-graphics\"],\"objc2-core-ml\":[\"dep:objc2-core-ml\"],\"objc2-core-video\":[\"dep:objc2-core-video\"],\"objc2-image-io\":[\"dep:objc2-image-io\"],\"objc2-io-surface\":[\"dep:objc2-io-surface\"],\"objc2-metal\":[\"dep:objc2-metal\"],\"objc2-open-gl\":[\"dep:objc2-open-gl\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-core-location_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\",\"objc2\"],\"name\":\"dispatch2\",\"optional\":true,\"req\":\">=0.3.0, <0.5.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CNPostalAddress\"],\"name\":\"objc2-contacts\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(target_os = \\\"tvos\\\"))\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"}],\"features\":{\"CLAvailability\":[],\"CLBackgroundActivitySession\":[],\"CLBeaconIdentityCondition\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSUUID\",\"objc2-foundation/NSValue\"],\"CLBeaconIdentityConstraint\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSUUID\"],\"CLBeaconRegion\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUUID\",\"objc2-foundation/NSValue\"],\"CLCircularGeographicCondition\":[\"objc2-foundation/NSObject\"],\"CLCircularRegion\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CLCondition\":[\"objc2-foundation/NSObject\"],\"CLError\":[\"objc2-foundation/NSString\"],\"CLErrorDomain\":[\"objc2-foundation/NSString\"],\"CLGeocoder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSString\"],\"CLHeading\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"CLLocation\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"CLLocationManager\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"CLLocationManagerDelegate\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\"],\"CLLocationManager_CLVisitExtensions\":[],\"CLLocationPushServiceError\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"CLLocationPushServiceExtension\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"CLLocationUpdater\":[],\"CLMonitor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"CLMonitorConfiguration\":[\"objc2-foundation/NSString\"],\"CLMonitoringEvent\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CLMonitoringRecord\":[\"objc2-foundation/NSObject\"],\"CLPlacemark\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTimeZone\"],\"CLRegion\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CLServiceSession\":[\"objc2-foundation/NSString\"],\"CLVisit\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"alloc\":[],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CLAvailability\",\"CLBackgroundActivitySession\",\"CLBeaconIdentityCondition\",\"CLBeaconIdentityConstraint\",\"CLBeaconRegion\",\"CLCircularGeographicCondition\",\"CLCircularRegion\",\"CLCondition\",\"CLError\",\"CLErrorDomain\",\"CLGeocoder\",\"CLHeading\",\"CLLocation\",\"CLLocationManager\",\"CLLocationManagerDelegate\",\"CLLocationManager_CLVisitExtensions\",\"CLLocationPushServiceError\",\"CLLocationPushServiceExtension\",\"CLLocationUpdater\",\"CLMonitor\",\"CLMonitorConfiguration\",\"CLMonitoringEvent\",\"CLMonitoringRecord\",\"CLPlacemark\",\"CLRegion\",\"CLServiceSession\",\"CLVisit\",\"block2\",\"dispatch2\",\"objc2-contacts\"],\"dispatch2\":[\"dep:dispatch2\"],\"objc2-contacts\":[\"dep:objc2-contacts\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-core-text_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"name\":\"objc2-core-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGContext\",\"CGFont\",\"CGImage\",\"CGPath\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.2\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"CTDefines\":[],\"CTFont\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFCharacterSet\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFString\"],\"CTFontCollection\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFSet\"],\"CTFontDescriptor\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFNumber\",\"objc2-core-foundation/CFSet\"],\"CTFontManager\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFBundle\",\"objc2-core-foundation/CFData\",\"objc2-core-foundation/CFDictionary\",\"objc2-core-foundation/CFError\",\"objc2-core-foundation/CFRunLoop\",\"objc2-core-foundation/CFURL\"],\"CTFontManagerErrors\":[],\"CTFontTraits\":[\"bitflags\"],\"CTFrame\":[\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CTFramesetter\":[\"objc2-core-foundation/CFAttributedString\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CTGlyphInfo\":[],\"CTLine\":[\"bitflags\",\"objc2-core-foundation/CFArray\",\"objc2-core-foundation/CFAttributedString\",\"objc2-core-foundation/CFCGTypes\"],\"CTParagraphStyle\":[],\"CTRubyAnnotation\":[\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CTRun\":[\"bitflags\",\"objc2-core-foundation/CFCGTypes\",\"objc2-core-foundation/CFDictionary\"],\"CTRunDelegate\":[\"objc2-core-foundation/CFCGTypes\"],\"CTStringAttributes\":[\"bitflags\"],\"CTTextTab\":[\"objc2-core-foundation/CFDictionary\"],\"CTTypesetter\":[\"objc2-core-foundation/CFAttributedString\",\"objc2-core-foundation/CFDictionary\"],\"SFNTLayoutTypes\":[],\"SFNTTypes\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CTDefines\",\"CTFont\",\"CTFontCollection\",\"CTFontDescriptor\",\"CTFontManager\",\"CTFontManagerErrors\",\"CTFontTraits\",\"CTFrame\",\"CTFramesetter\",\"CTGlyphInfo\",\"CTLine\",\"CTParagraphStyle\",\"CTRubyAnnotation\",\"CTRun\",\"CTRunDelegate\",\"CTStringAttributes\",\"CTTextTab\",\"CTTypesetter\",\"SFNTLayoutTypes\",\"SFNTTypes\",\"bitflags\",\"block2\",\"libc\",\"objc2\",\"objc2-core-graphics\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"objc2-core-foundation/objc2\",\"objc2-core-graphics?/objc2\"],\"objc2-core-graphics\":[\"dep:objc2-core-graphics\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", "objc2-encode_4.1.0": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", - "objc2-foundation_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"CFCGTypes\",\"CFRunLoop\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"AE\",\"AEDataModel\",\"objc2\"],\"name\":\"objc2-core-services\",\"optional\":true,\"req\":\"^0.3.1\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"FoundationErrors\":[],\"FoundationLegacySwiftCompatibility\":[],\"NSAffineTransform\":[],\"NSAppleEventDescriptor\":[\"bitflags\"],\"NSAppleEventManager\":[],\"NSAppleScript\":[],\"NSArchiver\":[],\"NSArray\":[\"bitflags\"],\"NSAttributedString\":[\"bitflags\"],\"NSAutoreleasePool\":[],\"NSBackgroundActivityScheduler\":[],\"NSBundle\":[],\"NSByteCountFormatter\":[\"bitflags\"],\"NSByteOrder\":[],\"NSCache\":[],\"NSCalendar\":[\"bitflags\"],\"NSCalendarDate\":[],\"NSCharacterSet\":[],\"NSClassDescription\":[],\"NSCoder\":[],\"NSComparisonPredicate\":[\"bitflags\"],\"NSCompoundPredicate\":[],\"NSConnection\":[],\"NSData\":[\"bitflags\"],\"NSDate\":[],\"NSDateComponentsFormatter\":[\"bitflags\"],\"NSDateFormatter\":[],\"NSDateInterval\":[],\"NSDateIntervalFormatter\":[],\"NSDebug\":[],\"NSDecimal\":[],\"NSDecimalNumber\":[],\"NSDictionary\":[],\"NSDistantObject\":[],\"NSDistributedLock\":[],\"NSDistributedNotificationCenter\":[\"bitflags\"],\"NSEnergyFormatter\":[],\"NSEnumerator\":[],\"NSError\":[],\"NSException\":[],\"NSExpression\":[],\"NSExtensionContext\":[],\"NSExtensionItem\":[],\"NSExtensionRequestHandling\":[],\"NSFileCoordinator\":[\"bitflags\"],\"NSFileHandle\":[],\"NSFileManager\":[\"bitflags\"],\"NSFilePresenter\":[],\"NSFileVersion\":[\"bitflags\"],\"NSFileWrapper\":[\"bitflags\"],\"NSFormatter\":[],\"NSGarbageCollector\":[],\"NSGeometry\":[\"bitflags\"],\"NSHFSFileTypes\":[],\"NSHTTPCookie\":[],\"NSHTTPCookieStorage\":[],\"NSHashTable\":[],\"NSHost\":[],\"NSISO8601DateFormatter\":[\"bitflags\"],\"NSIndexPath\":[],\"NSIndexSet\":[],\"NSInflectionRule\":[],\"NSInvocation\":[],\"NSItemProvider\":[\"bitflags\"],\"NSJSONSerialization\":[\"bitflags\"],\"NSKeyValueCoding\":[],\"NSKeyValueObserving\":[\"bitflags\"],\"NSKeyValueSharedObservers\":[],\"NSKeyedArchiver\":[],\"NSLengthFormatter\":[],\"NSLinguisticTagger\":[\"bitflags\"],\"NSListFormatter\":[],\"NSLocale\":[],\"NSLocalizedNumberFormatRule\":[],\"NSLock\":[],\"NSMapTable\":[],\"NSMassFormatter\":[],\"NSMeasurement\":[],\"NSMeasurementFormatter\":[\"bitflags\"],\"NSMetadata\":[],\"NSMetadataAttributes\":[],\"NSMethodSignature\":[],\"NSMorphology\":[],\"NSNetServices\":[\"bitflags\"],\"NSNotification\":[],\"NSNotificationQueue\":[\"bitflags\"],\"NSNull\":[],\"NSNumberFormatter\":[],\"NSObjCRuntime\":[\"bitflags\"],\"NSObject\":[],\"NSObjectScripting\":[],\"NSOperation\":[],\"NSOrderedCollectionChange\":[],\"NSOrderedCollectionDifference\":[\"bitflags\"],\"NSOrderedSet\":[],\"NSOrthography\":[],\"NSPathUtilities\":[\"bitflags\"],\"NSPersonNameComponents\":[],\"NSPersonNameComponentsFormatter\":[\"bitflags\"],\"NSPointerArray\":[],\"NSPointerFunctions\":[\"bitflags\"],\"NSPort\":[\"bitflags\"],\"NSPortCoder\":[],\"NSPortMessage\":[],\"NSPortNameServer\":[],\"NSPredicate\":[],\"NSProcessInfo\":[\"bitflags\"],\"NSProgress\":[],\"NSPropertyList\":[\"bitflags\"],\"NSProtocolChecker\":[],\"NSProxy\":[],\"NSRange\":[],\"NSRegularExpression\":[\"bitflags\"],\"NSRelativeDateTimeFormatter\":[],\"NSRunLoop\":[],\"NSScanner\":[],\"NSScriptClassDescription\":[],\"NSScriptCoercionHandler\":[],\"NSScriptCommand\":[],\"NSScriptCommandDescription\":[],\"NSScriptExecutionContext\":[],\"NSScriptKeyValueCoding\":[],\"NSScriptObjectSpecifiers\":[],\"NSScriptStandardSuiteCommands\":[],\"NSScriptSuiteRegistry\":[],\"NSScriptWhoseTests\":[],\"NSSet\":[],\"NSSortDescriptor\":[],\"NSSpellServer\":[],\"NSStream\":[\"bitflags\"],\"NSString\":[\"bitflags\"],\"NSTask\":[],\"NSTermOfAddress\":[],\"NSTextCheckingResult\":[\"bitflags\"],\"NSThread\":[],\"NSTimeZone\":[],\"NSTimer\":[],\"NSURL\":[\"bitflags\"],\"NSURLAuthenticationChallenge\":[],\"NSURLCache\":[],\"NSURLConnection\":[],\"NSURLCredential\":[],\"NSURLCredentialStorage\":[],\"NSURLDownload\":[],\"NSURLError\":[],\"NSURLHandle\":[],\"NSURLProtectionSpace\":[],\"NSURLProtocol\":[],\"NSURLRequest\":[],\"NSURLResponse\":[],\"NSURLSession\":[],\"NSUUID\":[],\"NSUbiquitousKeyValueStore\":[],\"NSUndoManager\":[],\"NSUnit\":[],\"NSUserActivity\":[],\"NSUserDefaults\":[],\"NSUserNotification\":[],\"NSUserScriptTask\":[],\"NSValue\":[],\"NSValueTransformer\":[],\"NSXMLDTD\":[],\"NSXMLDTDNode\":[],\"NSXMLDocument\":[],\"NSXMLElement\":[],\"NSXMLNode\":[],\"NSXMLNodeOptions\":[\"bitflags\"],\"NSXMLParser\":[],\"NSXPCConnection\":[\"bitflags\"],\"NSZone\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"FoundationErrors\",\"FoundationLegacySwiftCompatibility\",\"NSAffineTransform\",\"NSAppleEventDescriptor\",\"NSAppleEventManager\",\"NSAppleScript\",\"NSArchiver\",\"NSArray\",\"NSAttributedString\",\"NSAutoreleasePool\",\"NSBackgroundActivityScheduler\",\"NSBundle\",\"NSByteCountFormatter\",\"NSByteOrder\",\"NSCache\",\"NSCalendar\",\"NSCalendarDate\",\"NSCharacterSet\",\"NSClassDescription\",\"NSCoder\",\"NSComparisonPredicate\",\"NSCompoundPredicate\",\"NSConnection\",\"NSData\",\"NSDate\",\"NSDateComponentsFormatter\",\"NSDateFormatter\",\"NSDateInterval\",\"NSDateIntervalFormatter\",\"NSDebug\",\"NSDecimal\",\"NSDecimalNumber\",\"NSDictionary\",\"NSDistantObject\",\"NSDistributedLock\",\"NSDistributedNotificationCenter\",\"NSEnergyFormatter\",\"NSEnumerator\",\"NSError\",\"NSException\",\"NSExpression\",\"NSExtensionContext\",\"NSExtensionItem\",\"NSExtensionRequestHandling\",\"NSFileCoordinator\",\"NSFileHandle\",\"NSFileManager\",\"NSFilePresenter\",\"NSFileVersion\",\"NSFileWrapper\",\"NSFormatter\",\"NSGarbageCollector\",\"NSGeometry\",\"NSHFSFileTypes\",\"NSHTTPCookie\",\"NSHTTPCookieStorage\",\"NSHashTable\",\"NSHost\",\"NSISO8601DateFormatter\",\"NSIndexPath\",\"NSIndexSet\",\"NSInflectionRule\",\"NSInvocation\",\"NSItemProvider\",\"NSJSONSerialization\",\"NSKeyValueCoding\",\"NSKeyValueObserving\",\"NSKeyValueSharedObservers\",\"NSKeyedArchiver\",\"NSLengthFormatter\",\"NSLinguisticTagger\",\"NSListFormatter\",\"NSLocale\",\"NSLocalizedNumberFormatRule\",\"NSLock\",\"NSMapTable\",\"NSMassFormatter\",\"NSMeasurement\",\"NSMeasurementFormatter\",\"NSMetadata\",\"NSMetadataAttributes\",\"NSMethodSignature\",\"NSMorphology\",\"NSNetServices\",\"NSNotification\",\"NSNotificationQueue\",\"NSNull\",\"NSNumberFormatter\",\"NSObjCRuntime\",\"NSObject\",\"NSObjectScripting\",\"NSOperation\",\"NSOrderedCollectionChange\",\"NSOrderedCollectionDifference\",\"NSOrderedSet\",\"NSOrthography\",\"NSPathUtilities\",\"NSPersonNameComponents\",\"NSPersonNameComponentsFormatter\",\"NSPointerArray\",\"NSPointerFunctions\",\"NSPort\",\"NSPortCoder\",\"NSPortMessage\",\"NSPortNameServer\",\"NSPredicate\",\"NSProcessInfo\",\"NSProgress\",\"NSPropertyList\",\"NSProtocolChecker\",\"NSProxy\",\"NSRange\",\"NSRegularExpression\",\"NSRelativeDateTimeFormatter\",\"NSRunLoop\",\"NSScanner\",\"NSScriptClassDescription\",\"NSScriptCoercionHandler\",\"NSScriptCommand\",\"NSScriptCommandDescription\",\"NSScriptExecutionContext\",\"NSScriptKeyValueCoding\",\"NSScriptObjectSpecifiers\",\"NSScriptStandardSuiteCommands\",\"NSScriptSuiteRegistry\",\"NSScriptWhoseTests\",\"NSSet\",\"NSSortDescriptor\",\"NSSpellServer\",\"NSStream\",\"NSString\",\"NSTask\",\"NSTermOfAddress\",\"NSTextCheckingResult\",\"NSThread\",\"NSTimeZone\",\"NSTimer\",\"NSURL\",\"NSURLAuthenticationChallenge\",\"NSURLCache\",\"NSURLConnection\",\"NSURLCredential\",\"NSURLCredentialStorage\",\"NSURLDownload\",\"NSURLError\",\"NSURLHandle\",\"NSURLProtectionSpace\",\"NSURLProtocol\",\"NSURLRequest\",\"NSURLResponse\",\"NSURLSession\",\"NSUUID\",\"NSUbiquitousKeyValueStore\",\"NSUndoManager\",\"NSUnit\",\"NSUserActivity\",\"NSUserDefaults\",\"NSUserNotification\",\"NSUserScriptTask\",\"NSValue\",\"NSValueTransformer\",\"NSXMLDTD\",\"NSXMLDTDNode\",\"NSXMLDocument\",\"NSXMLElement\",\"NSXMLNode\",\"NSXMLNodeOptions\",\"NSXMLParser\",\"NSXPCConnection\",\"NSZone\",\"bitflags\",\"block2\",\"libc\",\"objc2-core-foundation\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\",\"block2?/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\",\"block2?/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\",\"block2?/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\",\"block2?/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\",\"block2?/gnustep-2-1\"],\"libc\":[\"dep:libc\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-services\":[\"dep:objc2-core-services\"],\"std\":[\"alloc\"],\"unstable-mutation-return-null\":[\"NSNull\"],\"unstable-static-nsstring\":[]}}", - "objc2-io-surface_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"CFDictionary\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"NSDictionary\",\"NSObject\",\"NSString\",\"alloc\"],\"name\":\"objc2-foundation\",\"optional\":true,\"req\":\"^0.3.1\"}],\"features\":{\"IOSurface\":[],\"IOSurfaceAPI\":[],\"IOSurfaceBase\":[],\"IOSurfaceRef\":[\"bitflags\"],\"IOSurfaceTypes\":[\"bitflags\"],\"ObjC\":[\"objc2\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"default\":[\"std\",\"IOSurface\",\"IOSurfaceAPI\",\"IOSurfaceBase\",\"IOSurfaceRef\",\"IOSurfaceTypes\",\"bitflags\",\"libc\",\"objc2\",\"objc2-core-foundation\",\"objc2-foundation\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"objc2-core-foundation?/objc2\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-foundation\":[\"dep:objc2-foundation\"],\"std\":[\"alloc\"]}}", - "objc2_0.6.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"backtrace\",\"req\":\"^0.3.74\"},{\"kind\":\"dev\",\"name\":\"core-foundation\",\"req\":\"^0.10.0\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"iai\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.158\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"objc2-encode\",\"req\":\"^4.1.0\"},{\"default_features\":false,\"name\":\"objc2-exception-helper\",\"optional\":true,\"req\":\"^0.1.1\"},{\"name\":\"objc2-proc-macros\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"alloc\":[\"objc2-encode/alloc\"],\"catch-all\":[\"exception\"],\"default\":[\"std\"],\"disable-encoding-assertions\":[],\"exception\":[\"dep:objc2-exception-helper\"],\"gnustep-1-7\":[\"unstable-static-class\",\"objc2-exception-helper?/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2-exception-helper?/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2-exception-helper?/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2-exception-helper?/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2-exception-helper?/gnustep-2-1\"],\"objc2-proc-macros\":[],\"relax-sign-encoding\":[],\"relax-void-encoding\":[],\"std\":[\"alloc\",\"objc2-encode/std\"],\"unstable-apple-new\":[],\"unstable-arbitrary-self-types\":[],\"unstable-autoreleasesafe\":[],\"unstable-coerce-pointee\":[],\"unstable-compiler-rt\":[\"gnustep-1-7\"],\"unstable-gnustep-strict-apple-compat\":[\"gnustep-1-7\"],\"unstable-objfw\":[],\"unstable-requires-macos\":[],\"unstable-static-class\":[\"dep:objc2-proc-macros\"],\"unstable-static-class-inlined\":[\"unstable-static-class\"],\"unstable-static-sel\":[\"dep:objc2-proc-macros\"],\"unstable-static-sel-inlined\":[\"unstable-static-sel\"],\"unstable-winobjc\":[\"gnustep-1-8\"],\"verify\":[]}}", - "object_0.36.7": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.2\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"default-hasher\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.4.1\"},{\"name\":\"ruzstd\",\"optional\":true,\"req\":\"^0.7.0\"},{\"default_features\":false,\"name\":\"wasmparser\",\"optional\":true,\"req\":\"^0.222.0\"}],\"features\":{\"all\":[\"read\",\"write\",\"build\",\"std\",\"compression\",\"wasm\"],\"archive\":[],\"build\":[\"build_core\",\"write_std\",\"elf\"],\"build_core\":[\"read_core\",\"write_core\"],\"cargo-all\":[],\"coff\":[],\"compression\":[\"dep:flate2\",\"dep:ruzstd\",\"std\"],\"default\":[\"read\",\"compression\"],\"doc\":[\"read_core\",\"write_std\",\"build_core\",\"std\",\"compression\",\"archive\",\"coff\",\"elf\",\"macho\",\"pe\",\"wasm\",\"xcoff\"],\"elf\":[],\"macho\":[],\"pe\":[\"coff\"],\"read\":[\"read_core\",\"archive\",\"coff\",\"elf\",\"macho\",\"pe\",\"xcoff\",\"unaligned\"],\"read_core\":[],\"rustc-dep-of-std\":[\"core\",\"compiler_builtins\",\"alloc\",\"memchr/rustc-dep-of-std\"],\"std\":[\"memchr/std\"],\"unaligned\":[],\"unstable\":[],\"unstable-all\":[\"all\",\"unstable\"],\"wasm\":[\"dep:wasmparser\"],\"write\":[\"write_std\",\"coff\",\"elf\",\"macho\",\"pe\",\"xcoff\"],\"write_core\":[\"dep:crc32fast\",\"dep:indexmap\",\"dep:hashbrown\"],\"write_std\":[\"write_core\",\"std\",\"indexmap?/std\",\"crc32fast?/std\"],\"xcoff\":[]}}", + "objc2-foundation_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CFArray\",\"CFAttributedString\",\"CFCGTypes\",\"CFCalendar\",\"CFCharacterSet\",\"CFData\",\"CFDate\",\"CFDictionary\",\"CFError\",\"CFFileSecurity\",\"CFLocale\",\"CFMachPort\",\"CFMessagePort\",\"CFRunLoop\",\"CFSet\",\"CFStream\",\"CFURL\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CFString\"],\"kind\":\"dev\",\"name\":\"objc2-core-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"AE\",\"AEDataModel\",\"objc2\"],\"name\":\"objc2-core-services\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"FoundationErrors\":[],\"FoundationLegacySwiftCompatibility\":[],\"NSAffineTransform\":[],\"NSAppleEventDescriptor\":[\"bitflags\"],\"NSAppleEventManager\":[],\"NSAppleScript\":[],\"NSArchiver\":[],\"NSArray\":[\"bitflags\"],\"NSAttributedString\":[\"bitflags\"],\"NSAutoreleasePool\":[],\"NSBackgroundActivityScheduler\":[],\"NSBundle\":[],\"NSByteCountFormatter\":[\"bitflags\"],\"NSByteOrder\":[],\"NSCache\":[],\"NSCalendar\":[\"bitflags\"],\"NSCalendarDate\":[],\"NSCharacterSet\":[],\"NSClassDescription\":[],\"NSCoder\":[],\"NSComparisonPredicate\":[\"bitflags\"],\"NSCompoundPredicate\":[],\"NSConnection\":[],\"NSData\":[\"bitflags\"],\"NSDate\":[],\"NSDateComponentsFormatter\":[\"bitflags\"],\"NSDateFormatter\":[],\"NSDateInterval\":[],\"NSDateIntervalFormatter\":[],\"NSDebug\":[],\"NSDecimal\":[],\"NSDecimalNumber\":[],\"NSDictionary\":[],\"NSDistantObject\":[],\"NSDistributedLock\":[],\"NSDistributedNotificationCenter\":[\"bitflags\"],\"NSEnergyFormatter\":[],\"NSEnumerator\":[],\"NSError\":[],\"NSException\":[],\"NSExpression\":[],\"NSExtensionContext\":[],\"NSExtensionItem\":[],\"NSExtensionRequestHandling\":[],\"NSFileCoordinator\":[\"bitflags\"],\"NSFileHandle\":[],\"NSFileManager\":[\"bitflags\"],\"NSFilePresenter\":[],\"NSFileVersion\":[\"bitflags\"],\"NSFileWrapper\":[\"bitflags\"],\"NSFormatter\":[],\"NSGarbageCollector\":[],\"NSGeometry\":[\"bitflags\"],\"NSHFSFileTypes\":[],\"NSHTTPCookie\":[],\"NSHTTPCookieStorage\":[],\"NSHashTable\":[],\"NSHost\":[],\"NSISO8601DateFormatter\":[\"bitflags\"],\"NSIndexPath\":[],\"NSIndexSet\":[],\"NSInflectionRule\":[],\"NSInvocation\":[],\"NSItemProvider\":[\"bitflags\"],\"NSJSONSerialization\":[\"bitflags\"],\"NSKeyValueCoding\":[],\"NSKeyValueObserving\":[\"bitflags\"],\"NSKeyValueSharedObservers\":[],\"NSKeyedArchiver\":[],\"NSLengthFormatter\":[],\"NSLinguisticTagger\":[\"bitflags\"],\"NSListFormatter\":[],\"NSLocale\":[],\"NSLocalizedNumberFormatRule\":[],\"NSLock\":[],\"NSMapTable\":[],\"NSMassFormatter\":[],\"NSMeasurement\":[\"NSUnit\"],\"NSMeasurementFormatter\":[\"bitflags\"],\"NSMetadata\":[],\"NSMetadataAttributes\":[],\"NSMethodSignature\":[],\"NSMorphology\":[],\"NSNetServices\":[\"bitflags\"],\"NSNotification\":[],\"NSNotificationQueue\":[\"bitflags\"],\"NSNull\":[],\"NSNumberFormatter\":[],\"NSObjCRuntime\":[\"bitflags\"],\"NSObject\":[],\"NSObjectScripting\":[],\"NSOperation\":[],\"NSOrderedCollectionChange\":[],\"NSOrderedCollectionDifference\":[\"bitflags\"],\"NSOrderedSet\":[],\"NSOrthography\":[],\"NSPathUtilities\":[\"bitflags\"],\"NSPersonNameComponents\":[],\"NSPersonNameComponentsFormatter\":[\"bitflags\"],\"NSPointerArray\":[],\"NSPointerFunctions\":[\"bitflags\"],\"NSPort\":[\"bitflags\"],\"NSPortCoder\":[],\"NSPortMessage\":[],\"NSPortNameServer\":[],\"NSPredicate\":[],\"NSProcessInfo\":[\"bitflags\"],\"NSProgress\":[],\"NSPropertyList\":[\"bitflags\"],\"NSProtocolChecker\":[],\"NSProxy\":[],\"NSRange\":[],\"NSRegularExpression\":[\"bitflags\"],\"NSRelativeDateTimeFormatter\":[],\"NSRunLoop\":[],\"NSScanner\":[],\"NSScriptClassDescription\":[],\"NSScriptCoercionHandler\":[],\"NSScriptCommand\":[],\"NSScriptCommandDescription\":[],\"NSScriptExecutionContext\":[],\"NSScriptKeyValueCoding\":[],\"NSScriptObjectSpecifiers\":[],\"NSScriptStandardSuiteCommands\":[],\"NSScriptSuiteRegistry\":[],\"NSScriptWhoseTests\":[],\"NSSet\":[],\"NSSortDescriptor\":[],\"NSSpellServer\":[],\"NSStream\":[\"bitflags\"],\"NSString\":[\"bitflags\"],\"NSTask\":[],\"NSTermOfAddress\":[],\"NSTextCheckingResult\":[\"bitflags\"],\"NSThread\":[],\"NSTimeZone\":[],\"NSTimer\":[],\"NSURL\":[\"bitflags\"],\"NSURLAuthenticationChallenge\":[],\"NSURLCache\":[],\"NSURLConnection\":[],\"NSURLCredential\":[],\"NSURLCredentialStorage\":[],\"NSURLDownload\":[],\"NSURLError\":[],\"NSURLHandle\":[],\"NSURLProtectionSpace\":[],\"NSURLProtocol\":[],\"NSURLRequest\":[],\"NSURLResponse\":[],\"NSURLSession\":[],\"NSUUID\":[],\"NSUbiquitousKeyValueStore\":[],\"NSUndoManager\":[],\"NSUnit\":[],\"NSUserActivity\":[],\"NSUserDefaults\":[],\"NSUserNotification\":[],\"NSUserScriptTask\":[],\"NSValue\":[],\"NSValueTransformer\":[],\"NSXMLDTD\":[],\"NSXMLDTDNode\":[],\"NSXMLDocument\":[],\"NSXMLElement\":[],\"NSXMLNode\":[],\"NSXMLNodeOptions\":[\"bitflags\"],\"NSXMLParser\":[],\"NSXPCConnection\":[\"bitflags\"],\"NSZone\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"FoundationErrors\",\"FoundationLegacySwiftCompatibility\",\"NSAffineTransform\",\"NSAppleEventDescriptor\",\"NSAppleEventManager\",\"NSAppleScript\",\"NSArchiver\",\"NSArray\",\"NSAttributedString\",\"NSAutoreleasePool\",\"NSBackgroundActivityScheduler\",\"NSBundle\",\"NSByteCountFormatter\",\"NSByteOrder\",\"NSCache\",\"NSCalendar\",\"NSCalendarDate\",\"NSCharacterSet\",\"NSClassDescription\",\"NSCoder\",\"NSComparisonPredicate\",\"NSCompoundPredicate\",\"NSConnection\",\"NSData\",\"NSDate\",\"NSDateComponentsFormatter\",\"NSDateFormatter\",\"NSDateInterval\",\"NSDateIntervalFormatter\",\"NSDebug\",\"NSDecimal\",\"NSDecimalNumber\",\"NSDictionary\",\"NSDistantObject\",\"NSDistributedLock\",\"NSDistributedNotificationCenter\",\"NSEnergyFormatter\",\"NSEnumerator\",\"NSError\",\"NSException\",\"NSExpression\",\"NSExtensionContext\",\"NSExtensionItem\",\"NSExtensionRequestHandling\",\"NSFileCoordinator\",\"NSFileHandle\",\"NSFileManager\",\"NSFilePresenter\",\"NSFileVersion\",\"NSFileWrapper\",\"NSFormatter\",\"NSGarbageCollector\",\"NSGeometry\",\"NSHFSFileTypes\",\"NSHTTPCookie\",\"NSHTTPCookieStorage\",\"NSHashTable\",\"NSHost\",\"NSISO8601DateFormatter\",\"NSIndexPath\",\"NSIndexSet\",\"NSInflectionRule\",\"NSInvocation\",\"NSItemProvider\",\"NSJSONSerialization\",\"NSKeyValueCoding\",\"NSKeyValueObserving\",\"NSKeyValueSharedObservers\",\"NSKeyedArchiver\",\"NSLengthFormatter\",\"NSLinguisticTagger\",\"NSListFormatter\",\"NSLocale\",\"NSLocalizedNumberFormatRule\",\"NSLock\",\"NSMapTable\",\"NSMassFormatter\",\"NSMeasurement\",\"NSMeasurementFormatter\",\"NSMetadata\",\"NSMetadataAttributes\",\"NSMethodSignature\",\"NSMorphology\",\"NSNetServices\",\"NSNotification\",\"NSNotificationQueue\",\"NSNull\",\"NSNumberFormatter\",\"NSObjCRuntime\",\"NSObject\",\"NSObjectScripting\",\"NSOperation\",\"NSOrderedCollectionChange\",\"NSOrderedCollectionDifference\",\"NSOrderedSet\",\"NSOrthography\",\"NSPathUtilities\",\"NSPersonNameComponents\",\"NSPersonNameComponentsFormatter\",\"NSPointerArray\",\"NSPointerFunctions\",\"NSPort\",\"NSPortCoder\",\"NSPortMessage\",\"NSPortNameServer\",\"NSPredicate\",\"NSProcessInfo\",\"NSProgress\",\"NSPropertyList\",\"NSProtocolChecker\",\"NSProxy\",\"NSRange\",\"NSRegularExpression\",\"NSRelativeDateTimeFormatter\",\"NSRunLoop\",\"NSScanner\",\"NSScriptClassDescription\",\"NSScriptCoercionHandler\",\"NSScriptCommand\",\"NSScriptCommandDescription\",\"NSScriptExecutionContext\",\"NSScriptKeyValueCoding\",\"NSScriptObjectSpecifiers\",\"NSScriptStandardSuiteCommands\",\"NSScriptSuiteRegistry\",\"NSScriptWhoseTests\",\"NSSet\",\"NSSortDescriptor\",\"NSSpellServer\",\"NSStream\",\"NSString\",\"NSTask\",\"NSTermOfAddress\",\"NSTextCheckingResult\",\"NSThread\",\"NSTimeZone\",\"NSTimer\",\"NSURL\",\"NSURLAuthenticationChallenge\",\"NSURLCache\",\"NSURLConnection\",\"NSURLCredential\",\"NSURLCredentialStorage\",\"NSURLDownload\",\"NSURLError\",\"NSURLHandle\",\"NSURLProtectionSpace\",\"NSURLProtocol\",\"NSURLRequest\",\"NSURLResponse\",\"NSURLSession\",\"NSUUID\",\"NSUbiquitousKeyValueStore\",\"NSUndoManager\",\"NSUnit\",\"NSUserActivity\",\"NSUserDefaults\",\"NSUserNotification\",\"NSUserScriptTask\",\"NSValue\",\"NSValueTransformer\",\"NSXMLDTD\",\"NSXMLDTDNode\",\"NSXMLDocument\",\"NSXMLElement\",\"NSXMLNode\",\"NSXMLNodeOptions\",\"NSXMLParser\",\"NSXPCConnection\",\"NSZone\",\"bitflags\",\"block2\",\"libc\",\"objc2-core-foundation\"],\"gnustep-1-7\":[\"objc2/gnustep-1-7\",\"block2?/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2/gnustep-1-8\",\"block2?/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2/gnustep-1-9\",\"block2?/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2/gnustep-2-0\",\"block2?/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2/gnustep-2-1\",\"block2?/gnustep-2-1\"],\"libc\":[\"dep:libc\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-services\":[\"dep:objc2-core-services\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[],\"unstable-mutation-return-null\":[\"NSNull\"],\"unstable-static-nsstring\":[]}}", + "objc2-io-surface_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"optional\":true,\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CFDictionary\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"NSDictionary\",\"NSObject\",\"NSString\",\"alloc\"],\"name\":\"objc2-foundation\",\"optional\":true,\"req\":\"^0.3.2\"}],\"features\":{\"IOSurface\":[],\"IOSurfaceAPI\":[],\"IOSurfaceBase\":[],\"IOSurfaceRef\":[\"bitflags\"],\"IOSurfaceTypes\":[\"bitflags\"],\"ObjC\":[\"objc2\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"default\":[\"std\",\"IOSurface\",\"IOSurfaceAPI\",\"IOSurfaceBase\",\"IOSurfaceRef\",\"IOSurfaceTypes\",\"bitflags\",\"libc\",\"objc2\",\"objc2-core-foundation\",\"objc2-foundation\"],\"libc\":[\"dep:libc\"],\"objc2\":[\"dep:objc2\",\"objc2-core-foundation?/objc2\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-foundation\":[\"dep:objc2-foundation\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-quartz-core_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.80\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CFCGTypes\",\"CFDate\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGColor\",\"CGColorSpace\",\"CGContext\",\"CGPath\",\"objc2\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CVBase\",\"objc2\"],\"name\":\"objc2-core-video\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"MTLAllocation\",\"MTLDevice\",\"MTLDrawable\",\"MTLPixelFormat\",\"MTLResidencySet\",\"MTLResource\",\"MTLTexture\"],\"name\":\"objc2-metal\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGLTypes\"],\"name\":\"objc2-open-gl\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(target_os = \\\"macos\\\")\"}],\"features\":{\"CAAnimation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"CABase\":[],\"CAConstraintLayoutManager\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CADisplayLink\":[\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSRunLoop\",\"objc2-foundation/NSString\"],\"CAEAGLLayer\":[],\"CAEDRMetadata\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\"],\"CAEmitterCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CAEmitterLayer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CAFrameRateRange\":[],\"CAGradientLayer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"CALayer\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNull\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CAMediaTiming\":[\"objc2-foundation/NSString\"],\"CAMediaTimingFunction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CAMetalDisplayLink\":[\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSRunLoop\",\"objc2-foundation/NSString\"],\"CAMetalLayer\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\"],\"CAOpenGLLayer\":[\"objc2-foundation/NSObject\"],\"CARemoteLayerClient\":[],\"CARemoteLayerServer\":[],\"CARenderer\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"CAReplicatorLayer\":[\"objc2-foundation/NSObject\"],\"CAScrollLayer\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CAShapeLayer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"CATextLayer\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CATiledLayer\":[\"objc2-foundation/NSObject\"],\"CATransaction\":[\"objc2-foundation/NSString\"],\"CATransform3D\":[\"objc2-foundation/NSValue\"],\"CATransformLayer\":[\"objc2-foundation/NSObject\"],\"CAValueFunction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"CoreAnimation\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"CAAnimation\",\"CABase\",\"CAConstraintLayoutManager\",\"CADisplayLink\",\"CAEAGLLayer\",\"CAEDRMetadata\",\"CAEmitterCell\",\"CAEmitterLayer\",\"CAFrameRateRange\",\"CAGradientLayer\",\"CALayer\",\"CAMediaTiming\",\"CAMediaTimingFunction\",\"CAMetalDisplayLink\",\"CAMetalLayer\",\"CAOpenGLLayer\",\"CARemoteLayerClient\",\"CARemoteLayerServer\",\"CARenderer\",\"CAReplicatorLayer\",\"CAScrollLayer\",\"CAShapeLayer\",\"CATextLayer\",\"CATiledLayer\",\"CATransaction\",\"CATransform3D\",\"CATransformLayer\",\"CAValueFunction\",\"CoreAnimation\",\"bitflags\",\"block2\",\"libc\",\"objc2-core-foundation\",\"objc2-core-graphics\",\"objc2-core-video\",\"objc2-metal\"],\"gnustep-1-7\":[],\"gnustep-1-8\":[],\"gnustep-1-9\":[],\"gnustep-2-0\":[],\"gnustep-2-1\":[],\"libc\":[\"dep:libc\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-graphics\":[\"dep:objc2-core-graphics\"],\"objc2-core-video\":[\"dep:objc2-core-video\"],\"objc2-metal\":[\"dep:objc2-metal\"],\"objc2-open-gl\":[\"dep:objc2-open-gl\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-ui-kit_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CKContainer\",\"CKRecord\",\"CKShare\",\"CKShareMetadata\"],\"name\":\"objc2-cloud-kit\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"NSManagedObjectContext\",\"NSManagedObjectModel\"],\"name\":\"objc2-core-data\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CFCGTypes\",\"CFDate\",\"objc2\"],\"name\":\"objc2-core-foundation\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CGColor\",\"CGContext\",\"CGFont\",\"CGImage\",\"CGPath\",\"objc2\"],\"name\":\"objc2-core-graphics\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CIColor\",\"CIImage\"],\"name\":\"objc2-core-image\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(target_os = \\\"watchos\\\"))\"},{\"default_features\":false,\"features\":[\"CLRegion\"],\"name\":\"objc2-core-location\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CTFont\",\"CTFontDescriptor\",\"objc2\"],\"name\":\"objc2-core-text\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"CADisplayLink\",\"CAFrameRateRange\",\"CALayer\",\"CAMediaTiming\",\"CATransform3D\",\"objc2-core-foundation\"],\"name\":\"objc2-quartz-core\",\"optional\":true,\"req\":\"^0.3.2\",\"target\":\"cfg(not(target_os = \\\"watchos\\\"))\"},{\"default_features\":false,\"features\":[\"NSSymbolEffect\"],\"name\":\"objc2-symbols\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"UTType\"],\"name\":\"objc2-uniform-type-identifiers\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"UNNotificationResponse\"],\"name\":\"objc2-user-notifications\",\"optional\":true,\"req\":\"^0.3.2\"}],\"features\":{\"DocumentManager\":[],\"NSAdaptiveImageGlyph\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSAttributedString\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"NSDataAsset\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSDiffableDataSourceSectionSnapshot\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSFileProviderExtension\":[],\"NSIndexPath_UIKitAdditions\":[\"objc2-foundation/NSIndexPath\"],\"NSItemProvider_UIKitAdditions\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\"],\"NSLayoutAnchor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSLayoutConstraint\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSLayoutManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSParagraphStyle\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCharacterSet\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSShadow\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSStringDrawing\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSText\":[],\"NSTextAttachment\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSFileWrapper\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextContainer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"NSTextContentManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\"],\"NSTextLayoutFragment\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\"],\"NSTextLayoutManager\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"NSTextLineFragment\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextList\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextListElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"NSTextRange\":[\"objc2-foundation/NSObjCRuntime\"],\"NSTextSelection\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"NSTextSelectionNavigation\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"NSTextStorage\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"NSTextViewportLayoutController\":[],\"NSToolbar_UIKitAdditions\":[],\"NSTouchBar_UIKitAdditions\":[],\"NSUserActivity_NSItemProvider\":[],\"PrintKitUI\":[],\"ShareSheet\":[],\"UIAccelerometer\":[\"objc2-foundation/NSDate\"],\"UIAccessibility\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIAccessibilityAdditions\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"UIAccessibilityConstants\":[\"bitflags\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UIAccessibilityContainer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSRange\"],\"UIAccessibilityContentSizeCategoryImageAdjusting\":[],\"UIAccessibilityCustomAction\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"UIAccessibilityCustomRotor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"UIAccessibilityElement\":[\"objc2-foundation/NSString\"],\"UIAccessibilityIdentification\":[\"objc2-foundation/NSString\"],\"UIAccessibilityLocationDescriptor\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"UIAccessibilityZoom\":[],\"UIAction\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIActionSheet\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIActivity\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UIActivityCollaborationModeRestriction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIActivityIndicatorView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIActivityItemProvider\":[\"objc2-foundation/NSOperation\",\"objc2-foundation/NSString\"],\"UIActivityItemsConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\"],\"UIActivityItemsConfigurationReading\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\"],\"UIActivityItemsConfigurationReading_ShareSheet\":[\"objc2-foundation/NSString\"],\"UIActivityViewController\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIAlert\":[],\"UIAlertController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIAlertView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIAppearance\":[\"objc2-foundation/NSArray\"],\"UIApplication\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUserActivity\"],\"UIApplicationShortcutItem\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIAttachmentBehavior\":[\"objc2-foundation/NSArray\"],\"UIBackgroundConfiguration\":[\"objc2-foundation/NSObject\"],\"UIBackgroundExtensionView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIBandSelectionInteraction\":[],\"UIBarAppearance\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIBarButtonItem\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIBarButtonItemAppearance\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIBarButtonItemBadge\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIBarButtonItemGroup\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIBarCommon\":[],\"UIBarItem\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIBehavioralStyle\":[],\"UIBezierPath\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIBlurEffect\":[\"objc2-foundation/NSObject\"],\"UIButton\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIButtonConfiguration\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICalendarSelection\":[],\"UICalendarSelectionMultiDate\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCalendar\"],\"UICalendarSelectionSingleDate\":[\"objc2-foundation/NSCalendar\"],\"UICalendarSelectionWeekOfYear\":[\"objc2-foundation/NSCalendar\"],\"UICalendarView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDateInterval\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTimeZone\"],\"UICalendarViewDecoration\":[],\"UICanvasFeedbackGenerator\":[],\"UICellAccessory\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICellConfigurationState\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UICloudSharingController\":[\"bitflags\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICollectionLayoutList\":[\"bitflags\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\"],\"UICollectionView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\"],\"UICollectionViewCell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICollectionViewCompositionalLayout\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICollectionViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICollectionViewFlowLayout\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\"],\"UICollectionViewItemRegistration\":[\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSString\"],\"UICollectionViewLayout\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICollectionViewListCell\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UICollectionViewTransitionLayout\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICollectionViewUpdateItem\":[\"objc2-foundation/NSIndexPath\"],\"UICollisionBehavior\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UIColor\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIColorPickerViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIColorWell\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UICommand\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIConfigurationColorTransformer\":[],\"UIConfigurationState\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIContentConfiguration\":[\"objc2-foundation/NSObject\"],\"UIContentSizeCategory\":[\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSString\"],\"UIContentSizeCategoryAdjusting\":[],\"UIContentUnavailableButtonProperties\":[\"objc2-foundation/NSObject\"],\"UIContentUnavailableConfiguration\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIContentUnavailableConfigurationState\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIContentUnavailableImageProperties\":[\"objc2-foundation/NSObject\"],\"UIContentUnavailableTextProperties\":[\"objc2-foundation/NSObject\"],\"UIContentUnavailableView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIContextMenuConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\"],\"UIContextMenuInteraction\":[\"objc2-foundation/NSObject\"],\"UIContextMenuSystem\":[],\"UIContextualAction\":[\"objc2-foundation/NSString\"],\"UIControl\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIConversationContext\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSPersonNameComponents\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIConversationEntry\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UICornerConfiguration\":[\"objc2-foundation/NSObject\"],\"UICornerRadius\":[\"objc2-foundation/NSObject\"],\"UIDataDetectors\":[\"bitflags\"],\"UIDataSourceTranslating\":[\"objc2-foundation/NSIndexPath\"],\"UIDatePicker\":[\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSTimeZone\"],\"UIDeferredMenuElement\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIDevice\":[\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUUID\"],\"UIDiffableDataSource\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOrderedCollectionDifference\",\"objc2-foundation/NSString\"],\"UIDocument\":[\"bitflags\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFilePresenter\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUndoManager\",\"objc2-foundation/NSUserActivity\"],\"UIDocumentBrowserAction\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIDocumentBrowserViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIDocumentInteractionController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIDocumentMenuViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIDocumentPickerExtensionViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIDocumentPickerViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIDocumentProperties\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSURL\"],\"UIDocumentViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIDocumentViewControllerLaunchOptions\":[\"objc2-foundation/NSString\"],\"UIDragInteraction\":[\"objc2-foundation/NSArray\"],\"UIDragItem\":[\"objc2-foundation/NSItemProvider\"],\"UIDragPreview\":[\"objc2-foundation/NSObject\"],\"UIDragPreviewParameters\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSValue\"],\"UIDragSession\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\"],\"UIDropInteraction\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\"],\"UIDynamicAnimator\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSString\"],\"UIDynamicBehavior\":[\"objc2-foundation/NSArray\"],\"UIDynamicItemBehavior\":[\"objc2-foundation/NSArray\"],\"UIEditMenuInteraction\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UIEvent\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSSet\"],\"UIEventAttribution\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIEventAttributionView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIFeedbackGenerator\":[],\"UIFieldBehavior\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\"],\"UIFindInteraction\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UIFindSession\":[\"objc2-foundation/NSString\"],\"UIFocus\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UIFocusAnimationCoordinator\":[\"objc2-foundation/NSDate\"],\"UIFocusDebugger\":[],\"UIFocusDefines\":[],\"UIFocusEffect\":[\"objc2-foundation/NSObject\"],\"UIFocusGuide\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UIFocusMovementHint\":[\"objc2-foundation/NSObject\"],\"UIFocusSystem\":[],\"UIFocusSystem_UIKitAdditions\":[\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIFocusUpdateContext_UIKitAdditions\":[],\"UIFont\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIFontDescriptor\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIFontMetrics\":[\"objc2-foundation/NSString\"],\"UIFontPickerViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIFontPickerViewControllerConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\"],\"UIFoundation\":[],\"UIGeometry\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UIGestureRecognizer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UIGestureRecognizerSubclass\":[\"objc2-foundation/NSSet\"],\"UIGlassEffect\":[\"objc2-foundation/NSObject\"],\"UIGraphics\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIGraphicsImageRenderer\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\"],\"UIGraphicsPDFRenderer\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIGraphicsRenderer\":[\"objc2-foundation/NSObject\"],\"UIGraphicsRendererSubclass\":[\"objc2-foundation/NSError\"],\"UIGravityBehavior\":[\"objc2-foundation/NSArray\"],\"UIGuidedAccess\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"UIGuidedAccessRestrictions\":[],\"UIHoverEffect\":[\"objc2-foundation/NSObject\"],\"UIHoverEffectLayer\":[\"objc2-foundation/NSObject\"],\"UIHoverGestureRecognizer\":[\"objc2-foundation/NSCoder\"],\"UIHoverStyle\":[\"objc2-foundation/NSObject\"],\"UIImage\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIImageAsset\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIImageConfiguration\":[\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\"],\"UIImagePickerController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UIImageReader\":[\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSURL\"],\"UIImageSymbolConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSLocale\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIImageView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"UIImpactFeedbackGenerator\":[],\"UIIndirectScribbleInteraction\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UIInputSuggestion\":[],\"UIInputView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIInputViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUUID\"],\"UIInteraction\":[\"objc2-foundation/NSArray\"],\"UIInterface\":[],\"UIKey\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIKeyCommand\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIKeyConstants\":[],\"UIKeyboardLayoutGuide\":[\"objc2-foundation/NSObject\"],\"UIKitCore\":[],\"UIKitDefines\":[],\"UILabel\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UILargeContentViewer\":[\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UILayoutGuide\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UILetterformAwareAdjusting\":[],\"UILexicon\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIListContentConfiguration\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIListContentImageProperties\":[\"objc2-foundation/NSObject\"],\"UIListContentTextProperties\":[\"objc2-foundation/NSObject\"],\"UIListSeparatorConfiguration\":[\"objc2-foundation/NSObject\"],\"UILocalNotification\":[\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSTimeZone\"],\"UILocalizedIndexedCollation\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UILongPressGestureRecognizer\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\"],\"UIMailConversationContext\":[\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIMailConversationEntry\":[\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIMainMenuSystem\":[\"objc2-foundation/NSObject\"],\"UIManagedDocument\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSFilePresenter\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIMenu\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIMenuBuilder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UIMenuController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UIMenuDisplayPreferences\":[\"objc2-foundation/NSObject\"],\"UIMenuElement\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIMenuLeaf\":[\"objc2-foundation/NSString\"],\"UIMenuSystem\":[],\"UIMessageConversationContext\":[],\"UIMessageConversationEntry\":[],\"UIMotionEffect\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UINavigationBar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UINavigationBarAppearance\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UINavigationController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UINavigationItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"UINib\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"UINibDeclarations\":[],\"UINibLoading\":[\"objc2-foundation/NSString\"],\"UINotificationFeedbackGenerator\":[],\"UIOpenURLContext\":[\"objc2-foundation/NSURL\"],\"UIOrientation\":[\"bitflags\"],\"UIPageControl\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIPageControlProgress\":[\"objc2-foundation/NSDate\"],\"UIPageViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIPanGestureRecognizer\":[\"bitflags\",\"objc2-foundation/NSCoder\"],\"UIPasteConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIPasteConfigurationSupporting\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSItemProvider\"],\"UIPasteControl\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIPasteboard\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIPencilInteraction\":[\"objc2-foundation/NSDate\"],\"UIPickerView\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIPinchGestureRecognizer\":[\"objc2-foundation/NSCoder\"],\"UIPointerAccessory\":[\"objc2-foundation/NSObject\"],\"UIPointerInteraction\":[],\"UIPointerLockState\":[\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UIPointerRegion\":[\"objc2-foundation/NSObject\"],\"UIPointerStyle\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UIPopoverBackgroundView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIPopoverController\":[\"objc2-foundation/NSArray\"],\"UIPopoverPresentationController\":[\"objc2-foundation/NSArray\"],\"UIPopoverPresentationControllerSourceItem\":[],\"UIPopoverSupport\":[\"bitflags\"],\"UIPresentationController\":[],\"UIPress\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\"],\"UIPressesEvent\":[\"objc2-foundation/NSSet\"],\"UIPreviewInteraction\":[],\"UIPreviewParameters\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSValue\"],\"UIPrintError\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"UIPrintFormatter\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIPrintInfo\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIPrintInteractionController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIPrintPageRenderer\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSRange\"],\"UIPrintPaper\":[\"objc2-foundation/NSArray\"],\"UIPrintServiceExtension\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSData\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIPrinter\":[\"bitflags\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UIPrinterPickerController\":[\"objc2-foundation/NSError\"],\"UIProgressView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\"],\"UIPushBehavior\":[\"objc2-foundation/NSArray\"],\"UIReferenceLibraryViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIRefreshControl\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIRegion\":[\"objc2-foundation/NSObject\"],\"UIResponder\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUndoManager\",\"objc2-foundation/NSUserActivity\"],\"UIResponder_UIActivityItemsConfiguration\":[],\"UIRotationGestureRecognizer\":[\"objc2-foundation/NSCoder\"],\"UIScene\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSUserActivity\"],\"UISceneActivationConditions\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSPredicate\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUserActivity\"],\"UISceneConfiguration\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISceneDefinitions\":[\"objc2-foundation/NSError\",\"objc2-foundation/NSString\"],\"UISceneDestructionCondition\":[\"objc2-foundation/NSObject\"],\"UISceneEnhancedStateRestoration\":[],\"UISceneOptions\":[\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUserActivity\"],\"UISceneSession\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUserActivity\",\"UISceneConfiguration\"],\"UISceneSessionActivationRequest\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUserActivity\"],\"UISceneSizeRestrictions\":[],\"UISceneSystemProtectionManager\":[\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UISceneWindowingBehaviors\":[],\"UISceneWindowingControlStyle\":[],\"UIScene_AVAudioSession\":[],\"UIScreen\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSString\"],\"UIScreenEdgePanGestureRecognizer\":[\"objc2-foundation/NSCoder\"],\"UIScreenMode\":[],\"UIScreenshotService\":[\"objc2-foundation/NSData\"],\"UIScribbleInteraction\":[],\"UIScrollEdgeElementContainerInteraction\":[],\"UIScrollView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\"],\"UISearchBar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UISearchContainerViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISearchController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISearchDisplayController\":[\"objc2-foundation/NSString\"],\"UISearchSuggestion\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSString\"],\"UISearchTab\":[\"objc2-foundation/NSString\"],\"UISearchTextField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISegmentedControl\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISelectionFeedbackGenerator\":[],\"UIShadowProperties\":[\"objc2-foundation/NSObject\"],\"UIShape\":[\"objc2-foundation/NSObject\"],\"UISheetPresentationController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UISlider\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UISliderTrackConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISmartReplySuggestion\":[\"objc2-foundation/NSString\"],\"UISnapBehavior\":[],\"UISplitViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISplitViewControllerLayoutEnvironment\":[],\"UISpringLoadedInteraction\":[],\"UISpringLoadedInteractionSupporting\":[],\"UIStackView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIStandardTextCursorView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIStateRestoration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSString\"],\"UIStatusBarManager\":[],\"UIStepper\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIStoryboard\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSString\"],\"UIStoryboardPopoverSegue\":[\"objc2-foundation/NSString\"],\"UIStoryboardSegue\":[\"objc2-foundation/NSString\"],\"UIStringDrawing\":[\"objc2-foundation/NSString\"],\"UISwipeActionsConfiguration\":[\"objc2-foundation/NSArray\"],\"UISwipeGestureRecognizer\":[\"bitflags\",\"objc2-foundation/NSCoder\"],\"UISwitch\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UISymbolContentTransition\":[\"objc2-foundation/NSObject\"],\"UISymbolEffectCompletion\":[],\"UITab\":[\"objc2-foundation/NSString\"],\"UITabAccessory\":[],\"UITabBar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UITabBarAppearance\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITabBarController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\"],\"UITabBarControllerSidebar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\"],\"UITabBarItem\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITabGroup\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UITabSidebarItem\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UITableView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSIndexPath\",\"objc2-foundation/NSIndexSet\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSProgress\",\"objc2-foundation/NSString\"],\"UITableViewCell\":[\"bitflags\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITableViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITableViewHeaderFooterView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITapGestureRecognizer\":[\"objc2-foundation/NSCoder\"],\"UITargetedDragPreview\":[\"objc2-foundation/NSObject\"],\"UITargetedPreview\":[\"objc2-foundation/NSObject\"],\"UITextChecker\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"UITextCursorDropPositionAnimator\":[],\"UITextCursorView\":[],\"UITextDragPreviewRenderer\":[\"objc2-foundation/NSRange\"],\"UITextDragURLPreviews\":[\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UITextDragging\":[\"bitflags\",\"objc2-foundation/NSArray\"],\"UITextDropProposal\":[\"objc2-foundation/NSObject\"],\"UITextDropping\":[\"objc2-foundation/NSProgress\"],\"UITextField\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UITextFormattingCoordinator\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSString\"],\"UITextFormattingViewController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITextFormattingViewControllerChangeValue\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UITextFormattingViewControllerComponent\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITextFormattingViewControllerConfiguration\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UITextFormattingViewControllerFormattingDescriptor\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UITextFormattingViewControllerFormattingStyle\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITextInput\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\"],\"UITextInputContext\":[],\"UITextInputTraits\":[\"bitflags\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITextInteraction\":[\"objc2-foundation/NSArray\"],\"UITextItem\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UITextItemInteraction\":[],\"UITextLoupeSession\":[],\"UITextPasteConfigurationSupporting\":[],\"UITextPasteDelegate\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSItemProvider\",\"objc2-foundation/NSString\"],\"UITextSearching\":[\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSOrderedSet\",\"objc2-foundation/NSString\"],\"UITextSelectionDisplayInteraction\":[\"objc2-foundation/NSArray\"],\"UITextSelectionHandleView\":[],\"UITextSelectionHighlightView\":[\"objc2-foundation/NSArray\"],\"UITextView\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSValue\"],\"UITimingCurveProvider\":[\"objc2-foundation/NSObject\"],\"UITimingParameters\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"UIToolTipInteraction\":[\"objc2-foundation/NSString\"],\"UIToolbar\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIToolbarAppearance\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UITouch\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSValue\"],\"UITrackingLayoutGuide\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\"],\"UITrait\":[\"objc2-foundation/NSString\"],\"UITraitCollection\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UITraitListEnvironment\":[],\"UIUpdateActionPhase\":[],\"UIUpdateInfo\":[\"objc2-foundation/NSDate\"],\"UIUpdateLink\":[],\"UIUserActivity\":[\"objc2-foundation/NSUserActivity\"],\"UIUserNotificationSettings\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"UIVibrancyEffect\":[\"objc2-foundation/NSObject\"],\"UIVideoEditorController\":[\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIView\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIViewAnimating\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"UIViewConfigurationState\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIViewController\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSBundle\",\"objc2-foundation/NSCoder\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSExtensionContext\",\"objc2-foundation/NSExtensionRequestHandling\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObjCRuntime\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIViewControllerTransition\":[],\"UIViewControllerTransitionCoordinator\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSString\"],\"UIViewControllerTransitioning\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIViewLayoutRegion\":[],\"UIViewPropertyAnimator\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"UIVisualEffect\":[\"objc2-foundation/NSObject\"],\"UIVisualEffectView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\"],\"UIWebView\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSData\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\",\"objc2-foundation/NSURLRequest\"],\"UIWindow\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSNotification\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIWindowScene\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSString\",\"UISceneSizeRestrictions\"],\"UIWindowSceneActivationAction\":[\"objc2-foundation/NSCoder\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UIWindowSceneActivationConfiguration\":[\"objc2-foundation/NSUserActivity\"],\"UIWindowSceneActivationInteraction\":[\"objc2-foundation/NSError\"],\"UIWindowSceneActivationRequestOptions\":[],\"UIWindowSceneDragInteraction\":[],\"UIWindowSceneGeometry\":[\"objc2-foundation/NSObject\"],\"UIWindowSceneGeometryPreferences\":[],\"UIWindowSceneGeometryPreferencesIOS\":[],\"UIWindowSceneGeometryPreferencesMac\":[],\"UIWindowSceneGeometryPreferencesVision\":[],\"UIWindowScenePlacement\":[\"objc2-foundation/NSObject\"],\"UIWindowSceneProminentPlacement\":[\"objc2-foundation/NSObject\"],\"UIWindowScenePushPlacement\":[\"objc2-foundation/NSObject\"],\"UIWindowSceneReplacePlacement\":[\"objc2-foundation/NSObject\"],\"UIWindowSceneStandardPlacement\":[\"objc2-foundation/NSObject\"],\"UIWritingToolsCoordinator\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSString\",\"objc2-foundation/NSUUID\",\"objc2-foundation/NSValue\"],\"UIWritingToolsCoordinatorAnimationParameters\":[],\"UIWritingToolsCoordinatorContext\":[\"objc2-foundation/NSAttributedString\",\"objc2-foundation/NSRange\",\"objc2-foundation/NSUUID\"],\"UIZoomTransitionOptions\":[\"objc2-foundation/NSObject\"],\"UNNotificationResponse_UIKitAdditions\":[],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"DocumentManager\",\"NSAdaptiveImageGlyph\",\"NSAttributedString\",\"NSDataAsset\",\"NSDiffableDataSourceSectionSnapshot\",\"NSFileProviderExtension\",\"NSIndexPath_UIKitAdditions\",\"NSItemProvider_UIKitAdditions\",\"NSLayoutAnchor\",\"NSLayoutConstraint\",\"NSLayoutManager\",\"NSParagraphStyle\",\"NSShadow\",\"NSStringDrawing\",\"NSText\",\"NSTextAttachment\",\"NSTextContainer\",\"NSTextContentManager\",\"NSTextElement\",\"NSTextLayoutFragment\",\"NSTextLayoutManager\",\"NSTextLineFragment\",\"NSTextList\",\"NSTextListElement\",\"NSTextRange\",\"NSTextSelection\",\"NSTextSelectionNavigation\",\"NSTextStorage\",\"NSTextViewportLayoutController\",\"NSToolbar_UIKitAdditions\",\"NSTouchBar_UIKitAdditions\",\"NSUserActivity_NSItemProvider\",\"PrintKitUI\",\"ShareSheet\",\"UIAccelerometer\",\"UIAccessibility\",\"UIAccessibilityAdditions\",\"UIAccessibilityConstants\",\"UIAccessibilityContainer\",\"UIAccessibilityContentSizeCategoryImageAdjusting\",\"UIAccessibilityCustomAction\",\"UIAccessibilityCustomRotor\",\"UIAccessibilityElement\",\"UIAccessibilityIdentification\",\"UIAccessibilityLocationDescriptor\",\"UIAccessibilityZoom\",\"UIAction\",\"UIActionSheet\",\"UIActivity\",\"UIActivityCollaborationModeRestriction\",\"UIActivityIndicatorView\",\"UIActivityItemProvider\",\"UIActivityItemsConfiguration\",\"UIActivityItemsConfigurationReading\",\"UIActivityItemsConfigurationReading_ShareSheet\",\"UIActivityViewController\",\"UIAlert\",\"UIAlertController\",\"UIAlertView\",\"UIAppearance\",\"UIApplication\",\"UIApplicationShortcutItem\",\"UIAttachmentBehavior\",\"UIBackgroundConfiguration\",\"UIBackgroundExtensionView\",\"UIBandSelectionInteraction\",\"UIBarAppearance\",\"UIBarButtonItem\",\"UIBarButtonItemAppearance\",\"UIBarButtonItemBadge\",\"UIBarButtonItemGroup\",\"UIBarCommon\",\"UIBarItem\",\"UIBehavioralStyle\",\"UIBezierPath\",\"UIBlurEffect\",\"UIButton\",\"UIButtonConfiguration\",\"UICalendarSelection\",\"UICalendarSelectionMultiDate\",\"UICalendarSelectionSingleDate\",\"UICalendarSelectionWeekOfYear\",\"UICalendarView\",\"UICalendarViewDecoration\",\"UICanvasFeedbackGenerator\",\"UICellAccessory\",\"UICellConfigurationState\",\"UICloudSharingController\",\"UICollectionLayoutList\",\"UICollectionView\",\"UICollectionViewCell\",\"UICollectionViewCompositionalLayout\",\"UICollectionViewController\",\"UICollectionViewFlowLayout\",\"UICollectionViewItemRegistration\",\"UICollectionViewLayout\",\"UICollectionViewListCell\",\"UICollectionViewTransitionLayout\",\"UICollectionViewUpdateItem\",\"UICollisionBehavior\",\"UIColor\",\"UIColorPickerViewController\",\"UIColorWell\",\"UICommand\",\"UIConfigurationColorTransformer\",\"UIConfigurationState\",\"UIContentConfiguration\",\"UIContentSizeCategory\",\"UIContentSizeCategoryAdjusting\",\"UIContentUnavailableButtonProperties\",\"UIContentUnavailableConfiguration\",\"UIContentUnavailableConfigurationState\",\"UIContentUnavailableImageProperties\",\"UIContentUnavailableTextProperties\",\"UIContentUnavailableView\",\"UIContextMenuConfiguration\",\"UIContextMenuInteraction\",\"UIContextMenuSystem\",\"UIContextualAction\",\"UIControl\",\"UIConversationContext\",\"UIConversationEntry\",\"UICornerConfiguration\",\"UICornerRadius\",\"UIDataDetectors\",\"UIDataSourceTranslating\",\"UIDatePicker\",\"UIDeferredMenuElement\",\"UIDevice\",\"UIDiffableDataSource\",\"UIDocument\",\"UIDocumentBrowserAction\",\"UIDocumentBrowserViewController\",\"UIDocumentInteractionController\",\"UIDocumentMenuViewController\",\"UIDocumentPickerExtensionViewController\",\"UIDocumentPickerViewController\",\"UIDocumentProperties\",\"UIDocumentViewController\",\"UIDocumentViewControllerLaunchOptions\",\"UIDragInteraction\",\"UIDragItem\",\"UIDragPreview\",\"UIDragPreviewParameters\",\"UIDragSession\",\"UIDropInteraction\",\"UIDynamicAnimator\",\"UIDynamicBehavior\",\"UIDynamicItemBehavior\",\"UIEditMenuInteraction\",\"UIEvent\",\"UIEventAttribution\",\"UIEventAttributionView\",\"UIFeedbackGenerator\",\"UIFieldBehavior\",\"UIFindInteraction\",\"UIFindSession\",\"UIFocus\",\"UIFocusAnimationCoordinator\",\"UIFocusDebugger\",\"UIFocusDefines\",\"UIFocusEffect\",\"UIFocusGuide\",\"UIFocusMovementHint\",\"UIFocusSystem\",\"UIFocusSystem_UIKitAdditions\",\"UIFocusUpdateContext_UIKitAdditions\",\"UIFont\",\"UIFontDescriptor\",\"UIFontMetrics\",\"UIFontPickerViewController\",\"UIFontPickerViewControllerConfiguration\",\"UIFoundation\",\"UIGeometry\",\"UIGestureRecognizer\",\"UIGestureRecognizerSubclass\",\"UIGlassEffect\",\"UIGraphics\",\"UIGraphicsImageRenderer\",\"UIGraphicsPDFRenderer\",\"UIGraphicsRenderer\",\"UIGraphicsRendererSubclass\",\"UIGravityBehavior\",\"UIGuidedAccess\",\"UIGuidedAccessRestrictions\",\"UIHoverEffect\",\"UIHoverEffectLayer\",\"UIHoverGestureRecognizer\",\"UIHoverStyle\",\"UIImage\",\"UIImageAsset\",\"UIImageConfiguration\",\"UIImagePickerController\",\"UIImageReader\",\"UIImageSymbolConfiguration\",\"UIImageView\",\"UIImpactFeedbackGenerator\",\"UIIndirectScribbleInteraction\",\"UIInputSuggestion\",\"UIInputView\",\"UIInputViewController\",\"UIInteraction\",\"UIInterface\",\"UIKey\",\"UIKeyCommand\",\"UIKeyConstants\",\"UIKeyboardLayoutGuide\",\"UIKitCore\",\"UIKitDefines\",\"UILabel\",\"UILargeContentViewer\",\"UILayoutGuide\",\"UILetterformAwareAdjusting\",\"UILexicon\",\"UIListContentConfiguration\",\"UIListContentImageProperties\",\"UIListContentTextProperties\",\"UIListSeparatorConfiguration\",\"UILocalNotification\",\"UILocalizedIndexedCollation\",\"UILongPressGestureRecognizer\",\"UIMailConversationContext\",\"UIMailConversationEntry\",\"UIMainMenuSystem\",\"UIManagedDocument\",\"UIMenu\",\"UIMenuBuilder\",\"UIMenuController\",\"UIMenuDisplayPreferences\",\"UIMenuElement\",\"UIMenuLeaf\",\"UIMenuSystem\",\"UIMessageConversationContext\",\"UIMessageConversationEntry\",\"UIMotionEffect\",\"UINavigationBar\",\"UINavigationBarAppearance\",\"UINavigationController\",\"UINavigationItem\",\"UINib\",\"UINibDeclarations\",\"UINibLoading\",\"UINotificationFeedbackGenerator\",\"UIOpenURLContext\",\"UIOrientation\",\"UIPageControl\",\"UIPageControlProgress\",\"UIPageViewController\",\"UIPanGestureRecognizer\",\"UIPasteConfiguration\",\"UIPasteConfigurationSupporting\",\"UIPasteControl\",\"UIPasteboard\",\"UIPencilInteraction\",\"UIPickerView\",\"UIPinchGestureRecognizer\",\"UIPointerAccessory\",\"UIPointerInteraction\",\"UIPointerLockState\",\"UIPointerRegion\",\"UIPointerStyle\",\"UIPopoverBackgroundView\",\"UIPopoverController\",\"UIPopoverPresentationController\",\"UIPopoverPresentationControllerSourceItem\",\"UIPopoverSupport\",\"UIPresentationController\",\"UIPress\",\"UIPressesEvent\",\"UIPreviewInteraction\",\"UIPreviewParameters\",\"UIPrintError\",\"UIPrintFormatter\",\"UIPrintInfo\",\"UIPrintInteractionController\",\"UIPrintPageRenderer\",\"UIPrintPaper\",\"UIPrintServiceExtension\",\"UIPrinter\",\"UIPrinterPickerController\",\"UIProgressView\",\"UIPushBehavior\",\"UIReferenceLibraryViewController\",\"UIRefreshControl\",\"UIRegion\",\"UIResponder\",\"UIResponder_UIActivityItemsConfiguration\",\"UIRotationGestureRecognizer\",\"UIScene\",\"UISceneActivationConditions\",\"UISceneConfiguration\",\"UISceneDefinitions\",\"UISceneDestructionCondition\",\"UISceneEnhancedStateRestoration\",\"UISceneOptions\",\"UISceneSession\",\"UISceneSessionActivationRequest\",\"UISceneSizeRestrictions\",\"UISceneSystemProtectionManager\",\"UISceneWindowingBehaviors\",\"UISceneWindowingControlStyle\",\"UIScene_AVAudioSession\",\"UIScreen\",\"UIScreenEdgePanGestureRecognizer\",\"UIScreenMode\",\"UIScreenshotService\",\"UIScribbleInteraction\",\"UIScrollEdgeElementContainerInteraction\",\"UIScrollView\",\"UISearchBar\",\"UISearchContainerViewController\",\"UISearchController\",\"UISearchDisplayController\",\"UISearchSuggestion\",\"UISearchTab\",\"UISearchTextField\",\"UISegmentedControl\",\"UISelectionFeedbackGenerator\",\"UIShadowProperties\",\"UIShape\",\"UISheetPresentationController\",\"UISlider\",\"UISliderTrackConfiguration\",\"UISmartReplySuggestion\",\"UISnapBehavior\",\"UISplitViewController\",\"UISplitViewControllerLayoutEnvironment\",\"UISpringLoadedInteraction\",\"UISpringLoadedInteractionSupporting\",\"UIStackView\",\"UIStandardTextCursorView\",\"UIStateRestoration\",\"UIStatusBarManager\",\"UIStepper\",\"UIStoryboard\",\"UIStoryboardPopoverSegue\",\"UIStoryboardSegue\",\"UIStringDrawing\",\"UISwipeActionsConfiguration\",\"UISwipeGestureRecognizer\",\"UISwitch\",\"UISymbolContentTransition\",\"UISymbolEffectCompletion\",\"UITab\",\"UITabAccessory\",\"UITabBar\",\"UITabBarAppearance\",\"UITabBarController\",\"UITabBarControllerSidebar\",\"UITabBarItem\",\"UITabGroup\",\"UITabSidebarItem\",\"UITableView\",\"UITableViewCell\",\"UITableViewController\",\"UITableViewHeaderFooterView\",\"UITapGestureRecognizer\",\"UITargetedDragPreview\",\"UITargetedPreview\",\"UITextChecker\",\"UITextCursorDropPositionAnimator\",\"UITextCursorView\",\"UITextDragPreviewRenderer\",\"UITextDragURLPreviews\",\"UITextDragging\",\"UITextDropProposal\",\"UITextDropping\",\"UITextField\",\"UITextFormattingCoordinator\",\"UITextFormattingViewController\",\"UITextFormattingViewControllerChangeValue\",\"UITextFormattingViewControllerComponent\",\"UITextFormattingViewControllerConfiguration\",\"UITextFormattingViewControllerFormattingDescriptor\",\"UITextFormattingViewControllerFormattingStyle\",\"UITextInput\",\"UITextInputContext\",\"UITextInputTraits\",\"UITextInteraction\",\"UITextItem\",\"UITextItemInteraction\",\"UITextLoupeSession\",\"UITextPasteConfigurationSupporting\",\"UITextPasteDelegate\",\"UITextSearching\",\"UITextSelectionDisplayInteraction\",\"UITextSelectionHandleView\",\"UITextSelectionHighlightView\",\"UITextView\",\"UITimingCurveProvider\",\"UITimingParameters\",\"UIToolTipInteraction\",\"UIToolbar\",\"UIToolbarAppearance\",\"UITouch\",\"UITrackingLayoutGuide\",\"UITrait\",\"UITraitCollection\",\"UITraitListEnvironment\",\"UIUpdateActionPhase\",\"UIUpdateInfo\",\"UIUpdateLink\",\"UIUserActivity\",\"UIUserNotificationSettings\",\"UIVibrancyEffect\",\"UIVideoEditorController\",\"UIView\",\"UIViewAnimating\",\"UIViewConfigurationState\",\"UIViewController\",\"UIViewControllerTransition\",\"UIViewControllerTransitionCoordinator\",\"UIViewControllerTransitioning\",\"UIViewLayoutRegion\",\"UIViewPropertyAnimator\",\"UIVisualEffect\",\"UIVisualEffectView\",\"UIWebView\",\"UIWindow\",\"UIWindowScene\",\"UIWindowSceneActivationAction\",\"UIWindowSceneActivationConfiguration\",\"UIWindowSceneActivationInteraction\",\"UIWindowSceneActivationRequestOptions\",\"UIWindowSceneDragInteraction\",\"UIWindowSceneGeometry\",\"UIWindowSceneGeometryPreferences\",\"UIWindowSceneGeometryPreferencesIOS\",\"UIWindowSceneGeometryPreferencesMac\",\"UIWindowSceneGeometryPreferencesVision\",\"UIWindowScenePlacement\",\"UIWindowSceneProminentPlacement\",\"UIWindowScenePushPlacement\",\"UIWindowSceneReplacePlacement\",\"UIWindowSceneStandardPlacement\",\"UIWritingToolsCoordinator\",\"UIWritingToolsCoordinatorAnimationParameters\",\"UIWritingToolsCoordinatorContext\",\"UIZoomTransitionOptions\",\"UNNotificationResponse_UIKitAdditions\",\"bitflags\",\"block2\",\"objc2-cloud-kit\",\"objc2-core-data\",\"objc2-core-foundation\",\"objc2-core-graphics\",\"objc2-core-image\",\"objc2-core-location\",\"objc2-core-text\",\"objc2-quartz-core\",\"objc2-user-notifications\"],\"objc2-cloud-kit\":[\"dep:objc2-cloud-kit\"],\"objc2-core-data\":[\"dep:objc2-core-data\"],\"objc2-core-foundation\":[\"dep:objc2-core-foundation\"],\"objc2-core-graphics\":[\"dep:objc2-core-graphics\"],\"objc2-core-image\":[\"dep:objc2-core-image\"],\"objc2-core-location\":[\"dep:objc2-core-location\"],\"objc2-core-text\":[\"dep:objc2-core-text\"],\"objc2-quartz-core\":[\"dep:objc2-quartz-core\"],\"objc2-symbols\":[\"dep:objc2-symbols\"],\"objc2-uniform-type-identifiers\":[\"dep:objc2-uniform-type-identifiers\"],\"objc2-user-notifications\":[\"dep:objc2-user-notifications\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2-user-notifications_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.5.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"block2\",\"optional\":true,\"req\":\">=0.6.1, <0.8.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"objc2\",\"req\":\">=0.6.2, <0.8.0\"},{\"default_features\":false,\"features\":[\"CLRegion\"],\"name\":\"objc2-core-location\",\"optional\":true,\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3.2\"}],\"features\":{\"NSString_UserNotifications\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSString\"],\"UNError\":[\"objc2-foundation/NSString\"],\"UNNotification\":[\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"UNNotificationAction\":[\"bitflags\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UNNotificationActionIcon\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UNNotificationAttachment\":[\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSURL\"],\"UNNotificationAttributedMessageContext\":[],\"UNNotificationCategory\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UNNotificationContent\":[\"objc2-foundation/NSArray\",\"objc2-foundation/NSDictionary\",\"objc2-foundation/NSError\",\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\",\"objc2-foundation/NSValue\"],\"UNNotificationRequest\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UNNotificationResponse\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UNNotificationServiceExtension\":[],\"UNNotificationSettings\":[\"objc2-foundation/NSObject\"],\"UNNotificationSound\":[\"objc2-foundation/NSObject\",\"objc2-foundation/NSString\"],\"UNNotificationTrigger\":[\"objc2-foundation/NSCalendar\",\"objc2-foundation/NSDate\",\"objc2-foundation/NSObject\"],\"UNUserNotificationCenter\":[\"bitflags\",\"objc2-foundation/NSArray\",\"objc2-foundation/NSError\",\"objc2-foundation/NSSet\",\"objc2-foundation/NSString\"],\"alloc\":[],\"bitflags\":[\"dep:bitflags\"],\"block2\":[\"dep:block2\"],\"default\":[\"std\",\"NSString_UserNotifications\",\"UNError\",\"UNNotification\",\"UNNotificationAction\",\"UNNotificationActionIcon\",\"UNNotificationAttachment\",\"UNNotificationAttributedMessageContext\",\"UNNotificationCategory\",\"UNNotificationContent\",\"UNNotificationRequest\",\"UNNotificationResponse\",\"UNNotificationServiceExtension\",\"UNNotificationSettings\",\"UNNotificationSound\",\"UNNotificationTrigger\",\"UNUserNotificationCenter\",\"bitflags\",\"block2\",\"objc2-core-location\"],\"objc2-core-location\":[\"dep:objc2-core-location\"],\"std\":[\"alloc\"],\"unstable-darwin-objc\":[]}}", + "objc2_0.6.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"backtrace\",\"req\":\"^0.3.74\"},{\"kind\":\"dev\",\"name\":\"core-foundation\",\"req\":\"^0.10.0\",\"target\":\"cfg(target_vendor = \\\"apple\\\")\"},{\"kind\":\"dev\",\"name\":\"iai\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.158\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"objc2-encode\",\"req\":\"^4.1.0\"},{\"default_features\":false,\"name\":\"objc2-exception-helper\",\"optional\":true,\"req\":\"^0.1.1\"},{\"name\":\"objc2-proc-macros\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"}],\"features\":{\"alloc\":[\"objc2-encode/alloc\"],\"catch-all\":[\"exception\"],\"default\":[\"std\"],\"disable-encoding-assertions\":[],\"exception\":[\"dep:objc2-exception-helper\"],\"gnustep-1-7\":[\"unstable-static-class\",\"objc2-exception-helper?/gnustep-1-7\"],\"gnustep-1-8\":[\"gnustep-1-7\",\"objc2-exception-helper?/gnustep-1-8\"],\"gnustep-1-9\":[\"gnustep-1-8\",\"objc2-exception-helper?/gnustep-1-9\"],\"gnustep-2-0\":[\"gnustep-1-9\",\"objc2-exception-helper?/gnustep-2-0\"],\"gnustep-2-1\":[\"gnustep-2-0\",\"objc2-exception-helper?/gnustep-2-1\"],\"objc2-proc-macros\":[],\"relax-sign-encoding\":[],\"relax-void-encoding\":[],\"std\":[\"alloc\",\"objc2-encode/std\"],\"unstable-apple-new\":[],\"unstable-arbitrary-self-types\":[],\"unstable-autoreleasesafe\":[],\"unstable-coerce-pointee\":[],\"unstable-compiler-rt\":[\"gnustep-1-7\"],\"unstable-gnustep-strict-apple-compat\":[\"gnustep-1-7\"],\"unstable-objfw\":[],\"unstable-requires-macos\":[],\"unstable-static-class\":[\"dep:objc2-proc-macros\"],\"unstable-static-class-inlined\":[\"unstable-static-class\"],\"unstable-static-sel\":[\"dep:objc2-proc-macros\"],\"unstable-static-sel-inlined\":[\"unstable-static-sel\"],\"unstable-winobjc\":[\"gnustep-1-8\"],\"verify\":[]}}", + "object_0.37.3": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.2\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"default-hasher\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.4.1\"},{\"name\":\"ruzstd\",\"optional\":true,\"req\":\"^0.8.1\"},{\"default_features\":false,\"name\":\"wasmparser\",\"optional\":true,\"req\":\"^0.236.0\"}],\"features\":{\"all\":[\"read\",\"write\",\"build\",\"std\",\"compression\",\"wasm\"],\"archive\":[],\"build\":[\"build_core\",\"write_std\",\"elf\"],\"build_core\":[\"read_core\",\"write_core\"],\"cargo-all\":[],\"coff\":[],\"compression\":[\"dep:flate2\",\"dep:ruzstd\",\"std\"],\"default\":[\"read\",\"compression\"],\"doc\":[\"read_core\",\"write_std\",\"build_core\",\"std\",\"compression\",\"archive\",\"coff\",\"elf\",\"macho\",\"pe\",\"wasm\",\"xcoff\"],\"elf\":[],\"macho\":[],\"pe\":[\"coff\"],\"read\":[\"read_core\",\"archive\",\"coff\",\"elf\",\"macho\",\"pe\",\"xcoff\",\"unaligned\"],\"read_core\":[],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"memchr/rustc-dep-of-std\"],\"std\":[\"memchr/std\"],\"unaligned\":[],\"unstable\":[],\"unstable-all\":[\"all\",\"unstable\"],\"wasm\":[\"dep:wasmparser\"],\"write\":[\"write_std\",\"coff\",\"elf\",\"macho\",\"pe\",\"xcoff\"],\"write_core\":[\"dep:crc32fast\",\"dep:indexmap\",\"dep:hashbrown\"],\"write_std\":[\"write_core\",\"std\",\"indexmap?/std\",\"crc32fast?/std\"],\"xcoff\":[]}}", "once_cell_1.21.3": "{\"dependencies\":[{\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1.1.3\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"critical-section\",\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"parking_lot_core\",\"optional\":true,\"req\":\"^0.9.10\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.6\"}],\"features\":{\"alloc\":[\"race\"],\"atomic-polyfill\":[\"critical-section\"],\"critical-section\":[\"dep:critical-section\",\"portable-atomic\"],\"default\":[\"std\"],\"parking_lot\":[\"dep:parking_lot_core\"],\"portable-atomic\":[\"dep:portable-atomic\"],\"race\":[],\"std\":[\"alloc\"],\"unstable\":[]}}", - "once_cell_polyfill_1.70.1": "{\"dependencies\":[],\"features\":{\"default\":[]}}", + "once_cell_polyfill_1.70.2": "{\"dependencies\":[],\"features\":{\"default\":[]}}", + "opaque-debug_0.3.1": "{\"dependencies\":[],\"features\":{}}", "openssl-macros_0.1.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "openssl-probe_0.1.6": "{\"dependencies\":[],\"features\":{}}", - "openssl-src_300.5.1+3.5.1": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.0.79\"}],\"features\":{\"camellia\":[],\"default\":[],\"force-engine\":[],\"idea\":[],\"ktls\":[],\"legacy\":[],\"no-dso\":[],\"seed\":[],\"ssl3\":[],\"weak-crypto\":[]}}", + "openssl-probe_0.2.1": "{\"dependencies\":[],\"features\":{}}", + "openssl-src_300.5.5+3.5.5": "{\"dependencies\":[{\"name\":\"cc\",\"req\":\"^1.0.79\"}],\"features\":{\"camellia\":[],\"default\":[],\"force-engine\":[],\"idea\":[],\"ktls\":[],\"legacy\":[],\"no-dso\":[],\"seed\":[],\"ssl3\":[],\"weak-crypto\":[]}}", "openssl-sys_0.9.111": "{\"dependencies\":[{\"features\":[\"ssl\",\"bindgen\"],\"name\":\"aws-lc-fips-sys\",\"optional\":true,\"req\":\"^0.13\"},{\"features\":[\"ssl\"],\"name\":\"aws-lc-sys\",\"optional\":true,\"req\":\"^0.27\"},{\"features\":[\"experimental\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72.0\"},{\"name\":\"bssl-sys\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.61\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"features\":[\"legacy\"],\"kind\":\"build\",\"name\":\"openssl-src\",\"optional\":true,\"req\":\"^300.2.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"},{\"kind\":\"build\",\"name\":\"vcpkg\",\"req\":\"^0.2.8\"}],\"features\":{\"aws-lc\":[\"dep:aws-lc-sys\"],\"aws-lc-fips\":[\"dep:aws-lc-fips-sys\"],\"unstable_boringssl\":[\"bssl-sys\"],\"vendored\":[\"openssl-src\"]}}", - "openssl_0.10.73": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.2.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"ffi\",\"package\":\"openssl-sys\",\"req\":\"^0.9.109\"},{\"name\":\"foreign-types\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.5.2\"},{\"name\":\"openssl-macros\",\"req\":\"^0.1.1\"}],\"features\":{\"aws-lc\":[\"ffi/aws-lc\"],\"bindgen\":[\"ffi/bindgen\"],\"default\":[],\"unstable_boringssl\":[\"ffi/unstable_boringssl\"],\"v101\":[],\"v102\":[],\"v110\":[],\"v111\":[],\"vendored\":[\"ffi/vendored\"]}}", + "openssl_0.10.75": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.2.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"ffi\",\"package\":\"openssl-sys\",\"req\":\"^0.9.111\"},{\"name\":\"foreign-types\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.5.2\"},{\"name\":\"openssl-macros\",\"req\":\"^0.1.1\"}],\"features\":{\"aws-lc\":[\"ffi/aws-lc\"],\"aws-lc-fips\":[\"ffi/aws-lc-fips\"],\"bindgen\":[\"ffi/bindgen\"],\"default\":[],\"unstable_boringssl\":[\"ffi/unstable_boringssl\"],\"v101\":[],\"v102\":[],\"v110\":[],\"v111\":[],\"vendored\":[\"ffi/vendored\"]}}", "opentelemetry-appender-tracing_0.31.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"features\":[\"logs\"],\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"logs\"],\"kind\":\"dev\",\"name\":\"opentelemetry-stdout\",\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"logs\",\"testing\",\"internal-logs\"],\"kind\":\"dev\",\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31\"},{\"features\":[\"flamegraph\",\"criterion\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.14\",\"target\":\"cfg(not(target_os = \\\"windows\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\">=0.1.40\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\">=0.1.40\"},{\"default_features\":false,\"name\":\"tracing-core\",\"req\":\">=0.1.33\"},{\"name\":\"tracing-log\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"tracing-log\",\"req\":\"^0.2\"},{\"name\":\"tracing-opentelemetry\",\"optional\":true,\"req\":\"^0.32\"},{\"default_features\":false,\"features\":[\"registry\",\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"env-filter\",\"registry\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"experimental_metadata_attributes\":[\"dep:tracing-log\"],\"experimental_use_tracing_span_context\":[\"tracing-opentelemetry\"],\"spec_unstable_logs_enabled\":[\"opentelemetry/spec_unstable_logs_enabled\"]}}", "opentelemetry-http_0.31.0": "{\"dependencies\":[{\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"hyper\",\"optional\":true,\"req\":\"^1.3\"},{\"features\":[\"client-legacy\",\"http1\",\"http2\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"time\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"default\":[\"internal-logs\"],\"hyper\":[\"dep:http-body-util\",\"dep:hyper\",\"dep:hyper-util\",\"dep:tokio\"],\"internal-logs\":[\"opentelemetry/internal-logs\"],\"reqwest\":[\"dep:reqwest\"],\"reqwest-blocking\":[\"dep:reqwest\",\"reqwest/blocking\"],\"reqwest-rustls\":[\"dep:reqwest\",\"reqwest/rustls-tls-native-roots\"],\"reqwest-rustls-webpki-roots\":[\"dep:reqwest\",\"reqwest/rustls-tls-webpki-roots\"]}}", "opentelemetry-otlp_0.31.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.1.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"http\",\"optional\":true,\"req\":\"^1.1\"},{\"default_features\":false,\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"opentelemetry-http\",\"optional\":true,\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"opentelemetry-proto\",\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"trace\",\"testing\"],\"kind\":\"dev\",\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31\"},{\"name\":\"prost\",\"optional\":true,\"req\":\"^0.14\"},{\"default_features\":false,\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"temp-env\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"sync\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"net\"],\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"tonic\",\"optional\":true,\"req\":\"^0.14.1\"},{\"default_features\":false,\"features\":[\"router\",\"server\"],\"kind\":\"dev\",\"name\":\"tonic\",\"req\":\"^0.14.1\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\">=0.1.40\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"}],\"features\":{\"default\":[\"http-proto\",\"reqwest-blocking-client\",\"trace\",\"metrics\",\"logs\",\"internal-logs\"],\"grpc-tonic\":[\"tonic\",\"prost\",\"http\",\"tokio\",\"opentelemetry-proto/gen-tonic\"],\"gzip-http\":[\"flate2\"],\"gzip-tonic\":[\"tonic/gzip\"],\"http-json\":[\"serde_json\",\"prost\",\"opentelemetry-http\",\"opentelemetry-proto/gen-tonic-messages\",\"opentelemetry-proto/with-serde\",\"http\",\"trace\",\"metrics\"],\"http-proto\":[\"prost\",\"opentelemetry-http\",\"opentelemetry-proto/gen-tonic-messages\",\"http\",\"trace\",\"metrics\"],\"hyper-client\":[\"opentelemetry-http/hyper\"],\"integration-testing\":[\"tonic\",\"prost\",\"tokio/full\",\"trace\",\"logs\"],\"internal-logs\":[\"tracing\",\"opentelemetry_sdk/internal-logs\",\"opentelemetry-http/internal-logs\"],\"logs\":[\"opentelemetry/logs\",\"opentelemetry_sdk/logs\",\"opentelemetry-proto/logs\"],\"metrics\":[\"opentelemetry/metrics\",\"opentelemetry_sdk/metrics\",\"opentelemetry-proto/metrics\"],\"reqwest-blocking-client\":[\"reqwest/blocking\",\"opentelemetry-http/reqwest-blocking\"],\"reqwest-client\":[\"reqwest\",\"opentelemetry-http/reqwest\"],\"reqwest-rustls\":[\"reqwest\",\"opentelemetry-http/reqwest-rustls\"],\"reqwest-rustls-webpki-roots\":[\"reqwest\",\"opentelemetry-http/reqwest-rustls-webpki-roots\"],\"serialize\":[\"serde\",\"serde_json\"],\"tls\":[\"tonic/tls-ring\"],\"tls-roots\":[\"tls\",\"tonic/tls-native-roots\"],\"tls-webpki-roots\":[\"tls\",\"tonic/tls-webpki-roots\"],\"trace\":[\"opentelemetry/trace\",\"opentelemetry_sdk/trace\",\"opentelemetry-proto/trace\"],\"zstd-http\":[\"zstd\"],\"zstd-tonic\":[\"tonic/zstd\"]}}", @@ -967,20 +1007,22 @@ "opentelemetry_sdk_0.31.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\",\"sink\",\"async-await-macro\"],\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"http\",\"optional\":true,\"req\":\"^1.1\"},{\"default_features\":false,\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"opentelemetry-http\",\"optional\":true,\"req\":\"^0.31\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.0\"},{\"features\":[\"flamegraph\",\"criterion\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.14\",\"target\":\"cfg(not(target_os = \\\"windows\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"std_rng\",\"small_rng\",\"os_rng\",\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.23.0\"},{\"default_features\":false,\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"temp-env\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"url\",\"optional\":true,\"req\":\"^2.5\"}],\"features\":{\"default\":[\"trace\",\"metrics\",\"logs\",\"internal-logs\"],\"experimental_async_runtime\":[],\"experimental_logs_batch_log_processor_with_async_runtime\":[\"logs\",\"experimental_async_runtime\"],\"experimental_logs_concurrent_log_processor\":[\"logs\"],\"experimental_metrics_custom_reader\":[\"metrics\"],\"experimental_metrics_disable_name_validation\":[\"metrics\"],\"experimental_metrics_periodicreader_with_async_runtime\":[\"metrics\",\"experimental_async_runtime\"],\"experimental_trace_batch_span_processor_with_async_runtime\":[\"tokio/sync\",\"trace\",\"experimental_async_runtime\"],\"internal-logs\":[\"opentelemetry/internal-logs\"],\"jaeger_remote_sampler\":[\"trace\",\"opentelemetry-http\",\"http\",\"serde\",\"serde_json\",\"url\",\"experimental_async_runtime\"],\"logs\":[\"opentelemetry/logs\"],\"metrics\":[\"opentelemetry/metrics\"],\"rt-tokio\":[\"tokio/rt\",\"tokio/time\",\"tokio-stream\",\"experimental_async_runtime\"],\"rt-tokio-current-thread\":[\"tokio/rt\",\"tokio/time\",\"tokio-stream\",\"experimental_async_runtime\"],\"spec_unstable_logs_enabled\":[\"logs\",\"opentelemetry/spec_unstable_logs_enabled\"],\"spec_unstable_metrics_views\":[\"metrics\"],\"testing\":[\"opentelemetry/testing\",\"trace\",\"metrics\",\"logs\",\"rt-tokio\",\"rt-tokio-current-thread\",\"tokio/macros\",\"tokio/rt-multi-thread\"],\"trace\":[\"opentelemetry/trace\",\"rand\",\"percent-encoding\"]}}", "option-ext_0.2.0": "{\"dependencies\":[],\"features\":{}}", "ordered-stream_0.2.0": "{\"dependencies\":[{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"futures-executor\",\"req\":\"^0.3.25\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.25\"}],\"features\":{}}", - "os_info_3.12.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"plist\",\"req\":\"^1.5.1\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_LibraryLoader\",\"Win32_System_Registry\",\"Win32_System_SystemInformation\",\"Win32_System_SystemServices\",\"Win32_System_Threading\",\"Win32_UI_WindowsAndMessaging\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"serde\"]}}", - "os_pipe_1.2.2": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.62\",\"target\":\"cfg(not(windows))\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Pipes\",\"Win32_Security\"],\"name\":\"windows-sys\",\"req\":\"^0.59.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"io_safety\":[]}}", - "owo-colors_4.2.2": "{\"dependencies\":[{\"name\":\"supports-color\",\"optional\":true,\"req\":\"^3.0.0\"},{\"name\":\"supports-color-2\",\"optional\":true,\"package\":\"supports-color\",\"req\":\"^2.0\"}],\"features\":{\"alloc\":[],\"supports-colors\":[\"dep:supports-color-2\",\"supports-color\"]}}", + "os_info_3.14.0": "{\"dependencies\":[{\"name\":\"android_system_properties\",\"req\":\"^0.1\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"feature\"],\"name\":\"nix\",\"req\":\"^0.30\",\"target\":\"cfg(any(target_os = \\\"aix\\\", target_os = \\\"dragonfly\\\", target_os = \\\"freebsd\\\", target_os = \\\"illumos\\\", target_os = \\\"linux\\\", target_os = \\\"macos\\\", target_os = \\\"netbsd\\\", target_os = \\\"openbsd\\\", target_os = \\\"cygwin\\\"))\"},{\"name\":\"objc2\",\"req\":\"^0.6\",\"target\":\"cfg(target_os = \\\"ios\\\")\"},{\"features\":[\"NSString\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3\",\"target\":\"cfg(target_os = \\\"ios\\\")\"},{\"features\":[\"NSData\",\"NSError\",\"NSEnumerator\",\"NSString\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"name\":\"objc2-ui-kit\",\"req\":\"^0.3\",\"target\":\"cfg(target_os = \\\"ios\\\")\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1\"},{\"name\":\"schemars\",\"optional\":true,\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_LibraryLoader\",\"Win32_System_Registry\",\"Win32_System_SystemInformation\",\"Win32_System_SystemServices\",\"Win32_System_Threading\",\"Win32_UI_WindowsAndMessaging\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"serde\"]}}", + "os_pipe_1.2.3": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.62\",\"target\":\"cfg(not(windows))\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Pipes\",\"Win32_Security\"],\"name\":\"windows-sys\",\"req\":\">=0.28, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"io_safety\":[]}}", + "owo-colors_4.2.3": "{\"dependencies\":[{\"name\":\"supports-color\",\"optional\":true,\"req\":\"^3.0.0\"},{\"name\":\"supports-color-2\",\"optional\":true,\"package\":\"supports-color\",\"req\":\"^2.0\"}],\"features\":{\"alloc\":[],\"supports-colors\":[\"dep:supports-color-2\",\"supports-color\"]}}", "parking_2.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{}}", - "parking_lot_0.12.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"name\":\"lock_api\",\"req\":\"^0.4.13\"},{\"name\":\"parking_lot_core\",\"req\":\"^0.9.11\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"}],\"features\":{\"arc_lock\":[\"lock_api/arc_lock\"],\"deadlock_detection\":[\"parking_lot_core/deadlock_detection\"],\"default\":[],\"hardware-lock-elision\":[],\"nightly\":[\"parking_lot_core/nightly\",\"lock_api/nightly\"],\"owning_ref\":[\"lock_api/owning_ref\"],\"send_guard\":[],\"serde\":[\"lock_api/serde\"]}}", - "parking_lot_core_0.9.11": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.60\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"libc\",\"req\":\"^0.2.95\",\"target\":\"cfg(unix)\"},{\"name\":\"petgraph\",\"optional\":true,\"req\":\"^0.6.0\"},{\"name\":\"redox_syscall\",\"req\":\"^0.5\",\"target\":\"cfg(target_os = \\\"redox\\\")\"},{\"name\":\"smallvec\",\"req\":\"^1.6.1\"},{\"name\":\"thread-id\",\"optional\":true,\"req\":\"^4.0.0\"},{\"name\":\"windows-targets\",\"req\":\"^0.52.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"deadlock_detection\":[\"petgraph\",\"thread-id\",\"backtrace\"],\"nightly\":[]}}", + "parking_lot_0.12.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"name\":\"lock_api\",\"req\":\"^0.4.14\"},{\"name\":\"parking_lot_core\",\"req\":\"^0.9.12\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"}],\"features\":{\"arc_lock\":[\"lock_api/arc_lock\"],\"deadlock_detection\":[\"parking_lot_core/deadlock_detection\"],\"default\":[],\"hardware-lock-elision\":[],\"nightly\":[\"parking_lot_core/nightly\",\"lock_api/nightly\"],\"owning_ref\":[\"lock_api/owning_ref\"],\"send_guard\":[],\"serde\":[\"lock_api/serde\"]}}", + "parking_lot_core_0.9.12": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.60\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"libc\",\"req\":\"^0.2.95\",\"target\":\"cfg(unix)\"},{\"name\":\"petgraph\",\"optional\":true,\"req\":\"^0.6.0\"},{\"name\":\"redox_syscall\",\"req\":\"^0.5\",\"target\":\"cfg(target_os = \\\"redox\\\")\"},{\"name\":\"smallvec\",\"req\":\"^1.6.1\"},{\"name\":\"windows-link\",\"req\":\"^0.2.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"deadlock_detection\":[\"petgraph\",\"backtrace\"],\"nightly\":[]}}", "paste_1.0.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}", - "pastey_0.2.0": "{\"dependencies\":[],\"features\":{}}", + "pastey_0.2.1": "{\"dependencies\":[],\"features\":{}}", "path-absolutize_3.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"path-dedot\",\"req\":\"^3.1.1\"},{\"kind\":\"dev\",\"name\":\"slash-formatter\",\"req\":\"^3\",\"target\":\"cfg(windows)\"}],\"features\":{\"lazy_static_cache\":[\"path-dedot/lazy_static_cache\"],\"once_cell_cache\":[\"path-dedot/once_cell_cache\"],\"unsafe_cache\":[\"path-dedot/unsafe_cache\"],\"use_unix_paths_on_wasm\":[\"path-dedot/use_unix_paths_on_wasm\"]}}", "path-dedot_3.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"lazy_static\",\"optional\":true,\"req\":\"^1.4\"},{\"name\":\"once_cell\",\"req\":\"^1.4\"}],\"features\":{\"lazy_static_cache\":[\"lazy_static\"],\"once_cell_cache\":[],\"unsafe_cache\":[],\"use_unix_paths_on_wasm\":[]}}", "pathdiff_0.2.3": "{\"dependencies\":[{\"name\":\"camino\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1.0.0\"}],\"features\":{}}", + "pbkdf2_0.12.2": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7\"},{\"default_features\":false,\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"hmac\"],\"parallel\":[\"rayon\",\"std\"],\"simple\":[\"hmac\",\"password-hash\",\"sha2\"],\"std\":[\"password-hash/std\"]}}", "pem-rfc7468_0.7.0": "{\"dependencies\":[{\"name\":\"base64ct\",\"req\":\"^1.4\"}],\"features\":{\"alloc\":[\"base64ct/alloc\"],\"std\":[\"alloc\",\"base64ct/std\"]}}", - "percent-encoding_2.3.1": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", + "percent-encoding_2.3.2": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "petgraph_0.6.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.7.2\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"kind\":\"dev\",\"name\":\"defmac\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"fixedbitset\",\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"name\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"odds\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.5.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.5.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"all\":[\"unstable\",\"quickcheck\",\"matrix_graph\",\"stable_graph\",\"graphmap\",\"rayon\"],\"default\":[\"graphmap\",\"stable_graph\",\"matrix_graph\"],\"generate\":[],\"graphmap\":[],\"matrix_graph\":[],\"rayon\":[\"dep:rayon\",\"indexmap/rayon\"],\"serde-1\":[\"serde\",\"serde_derive\"],\"stable_graph\":[],\"unstable\":[\"generate\"]}}", + "petgraph_0.8.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.7.2\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"kind\":\"dev\",\"name\":\"defmac\",\"req\":\"^0.2.1\"},{\"name\":\"dot-parser\",\"optional\":true,\"req\":\"^0.5.1\"},{\"name\":\"dot-parser-macros\",\"optional\":true,\"req\":\"^0.5.1\"},{\"default_features\":false,\"name\":\"fixedbitset\",\"req\":\"^0.5.7\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"odds\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.5.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.5.3\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"all\":[\"unstable\",\"quickcheck\",\"matrix_graph\",\"stable_graph\",\"graphmap\",\"rayon\",\"dot_parser\"],\"default\":[\"std\",\"graphmap\",\"stable_graph\",\"matrix_graph\"],\"dot_parser\":[\"std\",\"dep:dot-parser\",\"dep:dot-parser-macros\"],\"generate\":[],\"graphmap\":[],\"matrix_graph\":[],\"quickcheck\":[\"std\",\"dep:quickcheck\",\"graphmap\",\"stable_graph\"],\"rayon\":[\"std\",\"dep:rayon\",\"indexmap/rayon\",\"hashbrown/rayon\"],\"serde-1\":[\"serde\",\"serde_derive\"],\"stable_graph\":[\"serde?/alloc\"],\"std\":[\"indexmap/std\"],\"unstable\":[\"generate\"]}}", "phf_shared_0.11.3": "{\"dependencies\":[{\"name\":\"siphasher\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"uncased\",\"optional\":true,\"req\":\"^0.9.9\"},{\"name\":\"unicase\",\"optional\":true,\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "pin-project-internal_1.1.10": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1.0.25\"},{\"default_features\":false,\"features\":[\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\",\"full\",\"visit-mut\"],\"name\":\"syn\",\"req\":\"^2.0.1\"}],\"features\":{}}", "pin-project-lite_0.2.16": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"}],\"features\":{}}", @@ -990,11 +1032,11 @@ "pkcs1_0.7.5": "{\"dependencies\":[{\"features\":[\"db\"],\"kind\":\"dev\",\"name\":\"const-oid\",\"req\":\"^0.9\"},{\"features\":[\"oid\"],\"name\":\"der\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"pkcs8\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"spki\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"alloc\":[\"der/alloc\",\"zeroize\",\"pkcs8?/alloc\"],\"pem\":[\"alloc\",\"der/pem\",\"pkcs8?/pem\"],\"std\":[\"der/std\",\"alloc\"],\"zeroize\":[\"der/zeroize\"]}}", "pkcs8_0.10.2": "{\"dependencies\":[{\"features\":[\"oid\"],\"name\":\"der\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"},{\"name\":\"pkcs5\",\"optional\":true,\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"spki\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"name\":\"subtle\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"3des\":[\"encryption\",\"pkcs5/3des\"],\"alloc\":[\"der/alloc\",\"der/zeroize\",\"spki/alloc\"],\"des-insecure\":[\"encryption\",\"pkcs5/des-insecure\"],\"encryption\":[\"alloc\",\"pkcs5/alloc\",\"pkcs5/pbes2\",\"rand_core\"],\"getrandom\":[\"rand_core/getrandom\"],\"pem\":[\"alloc\",\"der/pem\",\"spki/pem\"],\"sha1-insecure\":[\"encryption\",\"pkcs5/sha1-insecure\"],\"std\":[\"alloc\",\"der/std\",\"spki/std\"]}}", "pkg-config_0.3.32": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"}],\"features\":{}}", - "plist_1.7.4": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22.0\"},{\"name\":\"indexmap\",\"req\":\"^2.1.0\"},{\"name\":\"quick_xml\",\"package\":\"quick-xml\",\"req\":\"^0.38.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.8.21\"},{\"features\":[\"parsing\",\"formatting\"],\"name\":\"time\",\"req\":\"^0.3.30\"}],\"features\":{\"default\":[\"serde\"],\"enable_unstable_features_that_may_break_with_minor_version_bumps\":[]}}", "png_0.18.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"approx\",\"req\":\"^0.5.1\"},{\"name\":\"bitflags\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"byteorder\",\"req\":\"^1.5.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.0\"},{\"name\":\"crc32fast\",\"req\":\"^1.2.0\"},{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"fdeflate\",\"req\":\"^0.3.3\"},{\"name\":\"flate2\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"features\":[\"simd\"],\"name\":\"miniz_oxide\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.2\"}],\"features\":{\"benchmarks\":[],\"unstable\":[\"crc32fast/nightly\"],\"zlib-rs\":[\"flate2/zlib-rs\"]}}", "polling_3.11.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"concurrent-queue\",\"req\":\"^2.2.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.1.0\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2.0.0\"},{\"name\":\"hermit-abi\",\"req\":\"^0.5.0\",\"target\":\"cfg(target_os = \\\"hermit\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.9\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"features\":[\"event\",\"fs\",\"pipe\",\"process\",\"std\",\"time\"],\"name\":\"rustix\",\"req\":\"^1.0.5\",\"target\":\"cfg(any(unix, target_os = \\\"fuchsia\\\", target_os = \\\"vxworks\\\"))\"},{\"kind\":\"dev\",\"name\":\"signal-hook\",\"req\":\"^0.3.17\",\"target\":\"cfg(all(unix, not(target_os=\\\"vita\\\")))\"},{\"kind\":\"dev\",\"name\":\"socket2\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.37\"},{\"features\":[\"Wdk_Foundation\",\"Wdk_Storage_FileSystem\",\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_IO\",\"Win32_System_LibraryLoader\",\"Win32_System_Threading\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "portable-atomic-util_0.2.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"build-context\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"portable-atomic\",\"req\":\"^1.5.1\"}],\"features\":{\"alloc\":[],\"default\":[],\"std\":[\"alloc\"]}}", - "portable-atomic_1.11.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"build-context\",\"req\":\"^0.1\"},{\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"=0.8.16\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"=0.2.163\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.60\"},{\"kind\":\"dev\",\"name\":\"sptr\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Threading\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"fallback\"],\"disable-fiq\":[],\"fallback\":[],\"float\":[],\"force-amo\":[],\"require-cas\":[],\"s-mode\":[],\"std\":[],\"unsafe-assume-single-core\":[]}}", + "poly1305_0.8.0": "{\"dependencies\":[{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"},{\"name\":\"opaque-debug\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"universal-hash\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"std\":[\"universal-hash/std\"]}}", + "portable-atomic-util_0.2.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"build-context\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"portable-atomic\",\"req\":\"^1.5.1\"}],\"features\":{\"alloc\":[],\"default\":[],\"std\":[\"alloc\"]}}", + "portable-atomic_1.13.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"build-context\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"^0.1\",\"target\":\"cfg(valgrind)\"},{\"name\":\"critical-section\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"crossbeam-utils\",\"req\":\"=0.8.16\"},{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"=0.2.163\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.60\"},{\"kind\":\"dev\",\"name\":\"sptr\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Threading\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"fallback\"],\"disable-fiq\":[],\"fallback\":[],\"float\":[],\"force-amo\":[],\"require-cas\":[],\"s-mode\":[],\"std\":[],\"unsafe-assume-privileged\":[],\"unsafe-assume-single-core\":[]}}", "portable-pty_0.9.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"bitflags\",\"req\":\"^1.3\",\"target\":\"cfg(windows)\"},{\"name\":\"downcast-rs\",\"req\":\"^1.0\"},{\"name\":\"filedescriptor\",\"req\":\"^0.8.3\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"lazy_static\",\"req\":\"^1.4\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"term\",\"fs\"],\"name\":\"nix\",\"req\":\"^0.28\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serial2\",\"req\":\"^0.2\"},{\"name\":\"shared_library\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"shell-words\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"smol\",\"req\":\"^2.0\"},{\"features\":[\"winuser\",\"consoleapi\",\"handleapi\",\"fileapi\",\"namedpipeapi\",\"synchapi\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"},{\"name\":\"winreg\",\"req\":\"^0.10\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"serde_support\":[\"serde\",\"serde_derive\"]}}", "potential_utf_0.1.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"name\":\"writeable\",\"optional\":true,\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"writeable/alloc\",\"zerovec?/alloc\"],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde_core\"],\"writeable\":[\"dep:writeable\"],\"zerovec\":[\"dep:zerovec\"]}}", "powerfmt_0.2.0": "{\"dependencies\":[{\"name\":\"powerfmt-macros\",\"optional\":true,\"req\":\"=0.1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"macros\"],\"macros\":[\"dep:powerfmt-macros\"],\"std\":[\"alloc\"]}}", @@ -1005,29 +1047,30 @@ "predicates_3.1.3": "{\"dependencies\":[{\"name\":\"anstyle\",\"req\":\"^1.0.0\"},{\"name\":\"difflib\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"float-cmp\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"normalize-line-endings\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"predicates-core\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"predicates-tree\",\"req\":\"^1.0\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"color\":[],\"default\":[\"diff\",\"regex\",\"float-cmp\",\"normalize-line-endings\",\"color\"],\"diff\":[\"dep:difflib\"],\"unstable\":[]}}", "pretty_assertions_1.4.1": "{\"dependencies\":[{\"name\":\"diff\",\"req\":\"^0.1.12\"},{\"name\":\"yansi\",\"req\":\"^1.0.1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[],\"unstable\":[]}}", "proc-macro-crate_3.4.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"^1.0.94\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0.39\"},{\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.99\"},{\"default_features\":false,\"features\":[\"parse\"],\"name\":\"toml_edit\",\"req\":\"^0.23.2\"}],\"features\":{}}", - "proc-macro2_1.0.95": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"proc-macro\"],\"nightly\":[],\"proc-macro\":[],\"span-locations\":[]}}", - "process-wrap_9.0.0": "{\"dependencies\":[{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"default_features\":false,\"features\":[\"fs\",\"poll\",\"signal\"],\"name\":\"nix\",\"optional\":true,\"req\":\"^0.30.1\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"remoteprocess\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.20.0\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.38.2\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\",\"rt-multi-thread\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.38.2\"},{\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.40\"},{\"name\":\"windows\",\"optional\":true,\"req\":\"^0.61.1\",\"target\":\"cfg(windows)\"}],\"features\":{\"creation-flags\":[\"dep:windows\",\"windows/Win32_System_Threading\"],\"default\":[\"creation-flags\",\"job-object\",\"kill-on-drop\",\"process-group\",\"process-session\",\"tracing\"],\"job-object\":[\"dep:windows\",\"windows/Win32_Security\",\"windows/Win32_System_Diagnostics_ToolHelp\",\"windows/Win32_System_IO\",\"windows/Win32_System_JobObjects\",\"windows/Win32_System_Threading\"],\"kill-on-drop\":[],\"process-group\":[],\"process-session\":[\"process-group\"],\"reset-sigmask\":[],\"std\":[\"dep:nix\"],\"tokio1\":[\"dep:nix\",\"dep:futures\",\"dep:tokio\"],\"tracing\":[\"dep:tracing\"]}}", + "proc-macro-error-attr2_2.0.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"}],\"features\":{}}", + "proc-macro-error2_2.0.1": "{\"dependencies\":[{\"name\":\"proc-macro-error-attr2\",\"req\":\"=2.0.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"syn\",\"optional\":true,\"req\":\"^2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.99\"}],\"features\":{\"default\":[\"syn-error\"],\"nightly\":[],\"syn-error\":[\"dep:syn\"]}}", + "proc-macro2_1.0.106": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"proc-macro\"],\"nightly\":[],\"proc-macro\":[],\"span-locations\":[]}}", + "process-wrap_9.0.1": "{\"dependencies\":[{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"indexmap\",\"req\":\"^2.9.0\"},{\"default_features\":false,\"features\":[\"fs\",\"poll\",\"signal\"],\"name\":\"nix\",\"optional\":true,\"req\":\"^0.30.1\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"remoteprocess\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.20.0\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.38.2\"},{\"features\":[\"io-util\",\"macros\",\"process\",\"rt\",\"rt-multi-thread\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.38.2\"},{\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.40\"},{\"name\":\"windows\",\"optional\":true,\"req\":\"^0.62.2\",\"target\":\"cfg(windows)\"}],\"features\":{\"creation-flags\":[\"dep:windows\",\"windows/Win32_System_Threading\"],\"default\":[\"creation-flags\",\"job-object\",\"kill-on-drop\",\"process-group\",\"process-session\",\"tracing\"],\"job-object\":[\"dep:windows\",\"windows/Win32_Security\",\"windows/Win32_System_Diagnostics_ToolHelp\",\"windows/Win32_System_IO\",\"windows/Win32_System_JobObjects\",\"windows/Win32_System_Threading\"],\"kill-on-drop\":[],\"process-group\":[],\"process-session\":[\"process-group\"],\"reset-sigmask\":[],\"std\":[\"dep:nix\"],\"tokio1\":[\"dep:nix\",\"dep:futures\",\"dep:tokio\"],\"tracing\":[\"dep:tracing\"]}}", "proptest_1.9.0": "{\"dependencies\":[{\"name\":\"bit-set\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"bit-vec\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.15\"},{\"name\":\"proptest-macro\",\"optional\":true,\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"rand\",\"req\":\"^0.9\"},{\"default_features\":false,\"name\":\"rand_chacha\",\"req\":\"^0.9\"},{\"name\":\"rand_xorshift\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rusty-fork\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"tempfile\",\"optional\":true,\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.112\"},{\"name\":\"unarray\",\"req\":\"^0.1.4\"},{\"name\":\"x86\",\"optional\":true,\"req\":\"^0.52.0\"}],\"features\":{\"alloc\":[],\"atomic64bit\":[],\"attr-macro\":[\"proptest-macro\"],\"bit-set\":[\"dep:bit-set\",\"dep:bit-vec\"],\"default\":[\"std\",\"fork\",\"timeout\",\"bit-set\"],\"default-code-coverage\":[\"std\",\"fork\",\"timeout\",\"bit-set\"],\"fork\":[\"std\",\"rusty-fork\",\"tempfile\"],\"handle-panics\":[\"std\"],\"hardware-rng\":[\"x86\"],\"no_std\":[\"num-traits/libm\"],\"std\":[\"rand/std\",\"rand/os_rng\",\"regex-syntax\",\"num-traits/std\"],\"timeout\":[\"fork\",\"rusty-fork/timeout\"],\"unstable\":[]}}", - "prost-derive_0.14.1": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.1\"},{\"name\":\"itertools\",\"req\":\">=0.10.1, <=0.14\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", - "prost_0.14.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"prost-derive\",\"optional\":true,\"req\":\"^0.14.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"default\":[\"derive\",\"std\"],\"derive\":[\"dep:prost-derive\"],\"no-recursion-limit\":[],\"std\":[]}}", + "prost-derive_0.14.3": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0.1\"},{\"name\":\"itertools\",\"req\":\">=0.10.1, <=0.14\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", + "prost_0.14.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"name\":\"prost-derive\",\"optional\":true,\"req\":\"^0.14.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"default\":[\"derive\",\"std\"],\"derive\":[\"dep:prost-derive\"],\"no-recursion-limit\":[],\"std\":[]}}", "psl-types_2.0.11": "{\"dependencies\":[],\"features\":{}}", - "psl_2.1.178": "{\"dependencies\":[{\"name\":\"psl-types\",\"req\":\"^2.0.11\"},{\"kind\":\"dev\",\"name\":\"rspec\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"helpers\"],\"helpers\":[]}}", + "psl_2.1.184": "{\"dependencies\":[{\"name\":\"psl-types\",\"req\":\"^2.0.11\"},{\"kind\":\"dev\",\"name\":\"rspec\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"helpers\"],\"helpers\":[]}}", "pulldown-cmark-escape_0.10.1": "{\"dependencies\":[],\"features\":{\"simd\":[]}}", "pulldown-cmark_0.10.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"name\":\"bitflags\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"getopts\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"name\":\"memchr\",\"req\":\"^2.5\"},{\"name\":\"pulldown-cmark-escape\",\"optional\":true,\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.6\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.61\"},{\"name\":\"unicase\",\"req\":\"^2.6\"}],\"features\":{\"default\":[\"getopts\",\"html\"],\"gen-tests\":[],\"html\":[\"pulldown-cmark-escape\"],\"simd\":[\"pulldown-cmark-escape?/simd\"]}}", - "pxfm_0.1.23": "{\"dependencies\":[{\"name\":\"num-traits\",\"req\":\"^0.2\"}],\"features\":{}}", + "pxfm_0.1.27": "{\"dependencies\":[{\"name\":\"num-traits\",\"req\":\"^0.2.3\"}],\"features\":{}}", "quick-error_2.0.1": "{\"dependencies\":[],\"features\":{}}", - "quick-xml_0.37.5": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"memchr\",\"req\":\"^2.1\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\">=1.0.139\"},{\"kind\":\"dev\",\"name\":\"serde-value\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.206\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.10\"},{\"default_features\":false,\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.21\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"}],\"features\":{\"async-tokio\":[\"tokio\"],\"default\":[],\"encoding\":[\"encoding_rs\"],\"escape-html\":[],\"overlapped-lists\":[],\"serde-types\":[\"serde/derive\"],\"serialize\":[\"serde\"]}}", - "quick-xml_0.38.0": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\">=0.4, <0.7\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"memchr\",\"req\":\"^2.1\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\">=1.0.139\"},{\"kind\":\"dev\",\"name\":\"serde-value\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.206\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.10\"},{\"default_features\":false,\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.21\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"}],\"features\":{\"async-tokio\":[\"tokio\"],\"default\":[],\"encoding\":[\"encoding_rs\"],\"escape-html\":[],\"overlapped-lists\":[],\"serde-types\":[\"serde/derive\"],\"serialize\":[\"serde\"]}}", + "quick-xml_0.38.4": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\">=0.4, <0.8\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"memchr\",\"req\":\"^2.1\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\">=1.0.139\"},{\"kind\":\"dev\",\"name\":\"serde-value\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.206\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.10\"},{\"default_features\":false,\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.21\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"}],\"features\":{\"async-tokio\":[\"tokio\"],\"default\":[],\"encoding\":[\"encoding_rs\"],\"escape-html\":[],\"overlapped-lists\":[],\"serde-types\":[\"serde/derive\"],\"serialize\":[\"serde\"]}}", "quinn-proto_0.11.13": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.1\"},{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.9\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"fastbloom\",\"optional\":true,\"req\":\"^0.14\"},{\"default_features\":false,\"features\":[\"wasm_js\"],\"name\":\"getrandom\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"lru-slab\",\"req\":\"^0.1.2\"},{\"name\":\"qlog\",\"optional\":true,\"req\":\"^0.15.2\"},{\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"features\":[\"wasm32_unknown_unknown_js\"],\"name\":\"ring\",\"req\":\"^0.17\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"name\":\"rustc-hash\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.5\"},{\"features\":[\"web\"],\"name\":\"rustls-pki-types\",\"req\":\"^1.7\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"rustls-platform-verifier\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"slab\",\"req\":\"^0.4.6\"},{\"name\":\"thiserror\",\"req\":\"^2.0.3\"},{\"features\":[\"alloc\",\"alloc\"],\"name\":\"tinyvec\",\"req\":\"^1.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.10\"},{\"default_features\":false,\"features\":[\"env-filter\",\"fmt\",\"ansi\",\"time\",\"local-time\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.45\"},{\"name\":\"web-time\",\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"}],\"features\":{\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs?/aws-lc-sys\",\"aws-lc-rs?/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"aws-lc-rs\",\"aws-lc-rs?/fips\"],\"bloom\":[\"dep:fastbloom\"],\"default\":[\"rustls-ring\",\"log\",\"bloom\"],\"log\":[\"tracing/log\"],\"platform-verifier\":[\"dep:rustls-platform-verifier\"],\"qlog\":[\"dep:qlog\"],\"ring\":[\"dep:ring\"],\"rustls\":[\"rustls-ring\"],\"rustls-aws-lc-rs\":[\"dep:rustls\",\"rustls?/aws-lc-rs\",\"aws-lc-rs\"],\"rustls-aws-lc-rs-fips\":[\"rustls-aws-lc-rs\",\"aws-lc-rs-fips\"],\"rustls-log\":[\"rustls?/logging\"],\"rustls-ring\":[\"dep:rustls\",\"rustls?/ring\",\"ring\"]}}", "quinn-udp_0.5.14": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cfg_aliases\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"async_tokio\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"name\":\"libc\",\"req\":\"^0.2.158\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.19\",\"target\":\"cfg(windows)\"},{\"name\":\"socket2\",\"req\":\">=0.5, <0.7\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"features\":[\"sync\",\"rt\",\"rt-multi-thread\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.28.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.10\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_IO\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.60\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"tracing\",\"log\"],\"direct-log\":[\"dep:log\"],\"fast-apple-datapath\":[],\"log\":[\"tracing/log\"]}}", "quinn_0.11.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.22\"},{\"name\":\"async-io\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"async-std\",\"optional\":true,\"req\":\"^1.11\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"cfg_aliases\",\"req\":\"^0.2\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4\"},{\"kind\":\"dev\",\"name\":\"crc\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"directories-next\",\"req\":\"^2\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.19\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"proto\",\"package\":\"quinn-proto\",\"req\":\"^0.11.12\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"rustc-hash\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.5\"},{\"kind\":\"dev\",\"name\":\"rustls-pemfile\",\"req\":\"^2\"},{\"name\":\"smol\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"socket2\",\"req\":\">=0.5, <0.7\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"thiserror\",\"req\":\"^2.0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1.28.1\"},{\"features\":[\"sync\",\"rt\",\"rt-multi-thread\",\"time\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.28.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.10\"},{\"default_features\":false,\"features\":[\"std-future\"],\"kind\":\"dev\",\"name\":\"tracing-futures\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"env-filter\",\"fmt\",\"ansi\",\"time\",\"local-time\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"tracing\"],\"name\":\"udp\",\"package\":\"quinn-udp\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2\"},{\"name\":\"web-time\",\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"}],\"features\":{\"aws-lc-rs\":[\"proto/aws-lc-rs\"],\"aws-lc-rs-fips\":[\"proto/aws-lc-rs-fips\"],\"bloom\":[\"proto/bloom\"],\"default\":[\"log\",\"platform-verifier\",\"runtime-tokio\",\"rustls-ring\",\"bloom\"],\"lock_tracking\":[],\"log\":[\"tracing/log\",\"proto/log\",\"udp/log\"],\"platform-verifier\":[\"proto/platform-verifier\"],\"qlog\":[\"proto/qlog\"],\"ring\":[\"proto/ring\"],\"runtime-async-std\":[\"async-io\",\"async-std\"],\"runtime-smol\":[\"async-io\",\"smol\"],\"runtime-tokio\":[\"tokio/time\",\"tokio/rt\",\"tokio/net\"],\"rustls\":[\"rustls-ring\"],\"rustls-aws-lc-rs\":[\"dep:rustls\",\"aws-lc-rs\",\"proto/rustls-aws-lc-rs\",\"proto/aws-lc-rs\"],\"rustls-aws-lc-rs-fips\":[\"dep:rustls\",\"aws-lc-rs-fips\",\"proto/rustls-aws-lc-rs-fips\",\"proto/aws-lc-rs-fips\"],\"rustls-log\":[\"rustls?/logging\"],\"rustls-ring\":[\"dep:rustls\",\"ring\",\"proto/rustls-ring\",\"proto/ring\"]}}", - "quote_1.0.40": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.66\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\"]}}", + "quote_1.0.44": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.80\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\"]}}", "r-efi_5.3.0": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"efiapi\":[],\"examples\":[\"native\"],\"native\":[],\"rustc-dep-of-std\":[\"core\"]}}", "radix_trie_0.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"endian-type\",\"req\":\"^0.1.2\"},{\"name\":\"nibble_vec\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{}}", "radix_trie_0.3.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"endian-type\",\"req\":\"^0.2.0\"},{\"name\":\"nibble_vec\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{}}", - "rama-boring-sys_0.5.9": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"req\":\"^0.72.0\"},{\"kind\":\"build\",\"name\":\"cmake\",\"req\":\"^0.1.54\"},{\"kind\":\"build\",\"name\":\"fs_extra\",\"req\":\"^1.3.0\"},{\"kind\":\"build\",\"name\":\"fslock\",\"req\":\"^0.2\"}],\"features\":{}}", - "rama-boring-tokio_0.5.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"rama-boring\",\"req\":\"^0.5.9\"},{\"name\":\"rama-boring-sys\",\"req\":\"^0.5.9\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"}],\"features\":{}}", - "rama-boring_0.5.9": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"kind\":\"dev\",\"name\":\"brotli\",\"req\":\"^8.0\"},{\"name\":\"foreign-types\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"openssl-macros\",\"req\":\"^0.1.1\"},{\"name\":\"rama-boring-sys\",\"req\":\"^0.5.9\"},{\"kind\":\"dev\",\"name\":\"rusty-hook\",\"req\":\"^0.11\"}],\"features\":{}}", + "rama-boring-sys_0.5.10": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"req\":\"^0.72.0\"},{\"kind\":\"build\",\"name\":\"cmake\",\"req\":\"^0.1.54\"},{\"kind\":\"build\",\"name\":\"fs_extra\",\"req\":\"^1.3.0\"},{\"kind\":\"build\",\"name\":\"fslock\",\"req\":\"^0.2\"}],\"features\":{}}", + "rama-boring-tokio_0.5.10": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"rama-boring\",\"req\":\"^0.5.10\"},{\"name\":\"rama-boring-sys\",\"req\":\"^0.5.10\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"}],\"features\":{}}", + "rama-boring_0.5.10": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.9\"},{\"kind\":\"dev\",\"name\":\"brotli\",\"req\":\"^8.0\"},{\"name\":\"foreign-types\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"openssl-macros\",\"req\":\"^0.1.1\"},{\"name\":\"rama-boring-sys\",\"req\":\"^0.5.10\"},{\"kind\":\"dev\",\"name\":\"rusty-hook\",\"req\":\"^0.11\"}],\"features\":{}}", "rama-core_0.3.0-alpha.4": "{\"dependencies\":[{\"name\":\"ahash\",\"req\":\"^0.8\"},{\"name\":\"asynk-strim\",\"req\":\"^0.1\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry\",\"optional\":true,\"req\":\"^0.31\"},{\"features\":[\"semconv_experimental\"],\"name\":\"opentelemetry-semantic-conventions\",\"optional\":true,\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"trace\",\"rt-tokio\"],\"name\":\"opentelemetry_sdk\",\"optional\":true,\"req\":\"^0.31\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"name\":\"rama-error\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"rama-macros\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"rama-utils\",\"req\":\"^0.3.0-alpha.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"macros\",\"fs\",\"io-std\"],\"name\":\"tokio\",\"req\":\"^1.48\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.48\"},{\"name\":\"tokio-graceful\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"features\":[\"codec\",\"io\",\"io-util\"],\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"tracing-opentelemetry\",\"optional\":true,\"req\":\"^0.32\"}],\"features\":{\"default\":[],\"opentelemetry\":[\"dep:opentelemetry\",\"dep:opentelemetry-semantic-conventions\",\"dep:opentelemetry_sdk\",\"dep:tracing-opentelemetry\"]}}", "rama-dns_0.3.0-alpha.4": "{\"dependencies\":[{\"name\":\"ahash\",\"req\":\"^0.8\"},{\"default_features\":false,\"features\":[\"tokio\",\"system-config\"],\"name\":\"hickory-resolver\",\"req\":\"^0.25\"},{\"name\":\"rama-core\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"rama-net\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"rama-utils\",\"req\":\"^0.3.0-alpha.4\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_html_form\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"macros\",\"net\"],\"name\":\"tokio\",\"req\":\"^1.48\"}],\"features\":{\"default\":[]}}", "rama-error_0.3.0-alpha.4": "{\"dependencies\":[],\"features\":{}}", @@ -1049,39 +1092,45 @@ "rand_chacha_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"simd\"],\"name\":\"ppv-lite86\",\"req\":\"^0.2.8\"},{\"name\":\"rand_core\",\"req\":\"^0.6.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde1\":[\"serde\"],\"simd\":[],\"std\":[\"ppv-lite86/std\"]}}", "rand_chacha_0.9.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"simd\"],\"name\":\"ppv-lite86\",\"req\":\"^0.2.14\"},{\"name\":\"rand_core\",\"req\":\"^0.9.0\"},{\"features\":[\"os_rng\"],\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.9.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"os_rng\":[\"rand_core/os_rng\"],\"serde\":[\"dep:serde\"],\"std\":[\"ppv-lite86/std\",\"rand_core/std\"]}}", "rand_core_0.6.4": "{\"dependencies\":[{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.2\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[],\"serde1\":[\"serde\"],\"std\":[\"alloc\",\"getrandom\",\"getrandom/std\"]}}", - "rand_core_0.9.3": "{\"dependencies\":[{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"os_rng\":[\"dep:getrandom\"],\"serde\":[\"dep:serde\"],\"std\":[\"getrandom?/std\"]}}", + "rand_core_0.9.5": "{\"dependencies\":[{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"os_rng\":[\"dep:getrandom\"],\"serde\":[\"dep:serde\"],\"std\":[\"getrandom?/std\"]}}", "rand_xorshift_0.4.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1\"},{\"name\":\"rand_core\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.118\"}],\"features\":{\"serde\":[\"dep:serde\"]}}", "ratatui-macros_0.6.0": "{\"dependencies\":[{\"features\":[\"user-hooks\"],\"kind\":\"dev\",\"name\":\"cargo-husky\",\"req\":\"^1.5.0\"},{\"name\":\"ratatui\",\"req\":\"^0.29.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.101\"}],\"features\":{}}", "rayon-core_1.13.0": "{\"dependencies\":[{\"name\":\"crossbeam-deque\",\"req\":\"^0.8.1\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"scoped-tls\",\"req\":\"^1.0\"},{\"name\":\"wasm_sync\",\"optional\":true,\"req\":\"^0.1.0\"}],\"features\":{\"web_spin_lock\":[\"dep:wasm_sync\"]}}", "rayon_1.11.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"either\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.4\"},{\"name\":\"rayon-core\",\"req\":\"^1.13.0\"},{\"name\":\"wasm_sync\",\"optional\":true,\"req\":\"^0.1.0\"}],\"features\":{\"web_spin_lock\":[\"dep:wasm_sync\",\"rayon-core/web_spin_lock\"]}}", - "redox_syscall_0.5.15": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{\"default\":[\"userspace\"],\"rustc-dep-of-std\":[\"core\",\"bitflags/rustc-dep-of-std\"],\"std\":[],\"userspace\":[]}}", + "redox_syscall_0.5.18": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{\"default\":[\"userspace\"],\"rustc-dep-of-std\":[\"core\",\"bitflags/rustc-dep-of-std\"],\"std\":[],\"userspace\":[]}}", + "redox_syscall_0.7.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.4\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{\"default\":[\"userspace\"],\"rustc-dep-of-std\":[\"core\",\"bitflags/rustc-dep-of-std\"],\"std\":[],\"userspace\":[]}}", "redox_users_0.4.6": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\",\"call\"],\"name\":\"libredox\",\"req\":\"^0.1.3\"},{\"name\":\"rust-argon2\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"auth\":[\"rust-argon2\",\"zeroize\"],\"default\":[\"auth\"]}}", - "redox_users_0.5.0": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\",\"call\"],\"name\":\"libredox\",\"req\":\"^0.1.3\"},{\"name\":\"rust-argon2\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"auth\":[\"rust-argon2\",\"zeroize\"],\"default\":[\"auth\"]}}", - "ref-cast-impl_1.0.24": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}", - "ref-cast_1.0.24": "{\"dependencies\":[{\"name\":\"ref-cast-impl\",\"req\":\"=1.0.24\"},{\"kind\":\"dev\",\"name\":\"ref-cast-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.81\"}],\"features\":{}}", + "redox_users_0.5.2": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\",\"call\"],\"name\":\"libredox\",\"req\":\"^0.1.3\"},{\"name\":\"rust-argon2\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.4\"}],\"features\":{\"auth\":[\"rust-argon2\",\"zeroize\"],\"default\":[\"auth\"]}}", + "ref-cast-impl_1.0.25": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}", + "ref-cast_1.0.25": "{\"dependencies\":[{\"name\":\"ref-cast-impl\",\"req\":\"=1.0.25\"},{\"kind\":\"dev\",\"name\":\"ref-cast-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{}}", "regex-automata_0.4.13": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aho-corasick\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"default_features\":false,\"features\":[\"atty\",\"humantime\",\"termcolor\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.14\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.6.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"syntax\",\"perf\",\"unicode\",\"meta\",\"nfa\",\"dfa\",\"hybrid\"],\"dfa\":[\"dfa-build\",\"dfa-search\",\"dfa-onepass\"],\"dfa-build\":[\"nfa-thompson\",\"dfa-search\"],\"dfa-onepass\":[\"nfa-thompson\"],\"dfa-search\":[],\"hybrid\":[\"alloc\",\"nfa-thompson\"],\"internal-instrument\":[\"internal-instrument-pikevm\"],\"internal-instrument-pikevm\":[\"logging\",\"std\"],\"logging\":[\"dep:log\",\"aho-corasick?/logging\",\"memchr?/logging\"],\"meta\":[\"syntax\",\"nfa-pikevm\"],\"nfa\":[\"nfa-thompson\",\"nfa-pikevm\",\"nfa-backtrack\"],\"nfa-backtrack\":[\"nfa-thompson\"],\"nfa-pikevm\":[\"nfa-thompson\"],\"nfa-thompson\":[\"alloc\"],\"perf\":[\"perf-inline\",\"perf-literal\"],\"perf-inline\":[],\"perf-literal\":[\"perf-literal-substring\",\"perf-literal-multisubstring\"],\"perf-literal-multisubstring\":[\"dep:aho-corasick\"],\"perf-literal-substring\":[\"aho-corasick?/perf-literal\",\"dep:memchr\"],\"std\":[\"regex-syntax?/std\",\"memchr?/std\",\"aho-corasick?/std\",\"alloc\"],\"syntax\":[\"dep:regex-syntax\",\"alloc\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\",\"unicode-word-boundary\",\"regex-syntax?/unicode\"],\"unicode-age\":[\"regex-syntax?/unicode-age\"],\"unicode-bool\":[\"regex-syntax?/unicode-bool\"],\"unicode-case\":[\"regex-syntax?/unicode-case\"],\"unicode-gencat\":[\"regex-syntax?/unicode-gencat\"],\"unicode-perl\":[\"regex-syntax?/unicode-perl\"],\"unicode-script\":[\"regex-syntax?/unicode-script\"],\"unicode-segment\":[\"regex-syntax?/unicode-segment\"],\"unicode-word-boundary\":[]}}", "regex-lite_0.1.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"default\":[\"std\",\"string\"],\"std\":[],\"string\":[]}}", "regex-syntax_0.6.29": "{\"dependencies\":[],\"features\":{\"default\":[\"unicode\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", - "regex-syntax_0.8.5": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.0\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"std\",\"unicode\"],\"std\":[],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", + "regex-syntax_0.8.8": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.0\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"std\",\"unicode\"],\"std\":[],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", "regex_1.12.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aho-corasick\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"atty\",\"humantime\",\"termcolor\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.6.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"syntax\",\"meta\",\"nfa-pikevm\"],\"name\":\"regex-automata\",\"req\":\"^0.4.12\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"default\":[\"std\",\"perf\",\"unicode\",\"regex-syntax/default\"],\"logging\":[\"aho-corasick?/logging\",\"memchr?/logging\",\"regex-automata/logging\"],\"pattern\":[],\"perf\":[\"perf-cache\",\"perf-dfa\",\"perf-onepass\",\"perf-backtrack\",\"perf-inline\",\"perf-literal\"],\"perf-backtrack\":[\"regex-automata/nfa-backtrack\"],\"perf-cache\":[],\"perf-dfa\":[\"regex-automata/hybrid\"],\"perf-dfa-full\":[\"regex-automata/dfa-build\",\"regex-automata/dfa-search\"],\"perf-inline\":[\"regex-automata/perf-inline\"],\"perf-literal\":[\"dep:aho-corasick\",\"dep:memchr\",\"regex-automata/perf-literal\"],\"perf-onepass\":[\"regex-automata/dfa-onepass\"],\"std\":[\"aho-corasick?/std\",\"memchr?/std\",\"regex-automata/std\",\"regex-syntax/std\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\",\"regex-automata/unicode\",\"regex-syntax/unicode\"],\"unicode-age\":[\"regex-automata/unicode-age\",\"regex-syntax/unicode-age\"],\"unicode-bool\":[\"regex-automata/unicode-bool\",\"regex-syntax/unicode-bool\"],\"unicode-case\":[\"regex-automata/unicode-case\",\"regex-syntax/unicode-case\"],\"unicode-gencat\":[\"regex-automata/unicode-gencat\",\"regex-syntax/unicode-gencat\"],\"unicode-perl\":[\"regex-automata/unicode-perl\",\"regex-automata/unicode-word-boundary\",\"regex-syntax/unicode-perl\"],\"unicode-script\":[\"regex-automata/unicode-script\",\"regex-syntax/unicode-script\"],\"unicode-segment\":[\"regex-automata/unicode-segment\",\"regex-syntax/unicode-segment\"],\"unstable\":[\"pattern\"],\"use_std\":[\"std\"]}}", - "reqwest_0.12.24": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"tokio\"],\"name\":\"async-compression\",\"optional\":true,\"req\":\"^0.4.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"brotli_crate\",\"package\":\"brotli\",\"req\":\"^8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"cookie_crate\",\"optional\":true,\"package\":\"cookie\",\"req\":\"^0.18.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.21.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.28\"},{\"default_features\":false,\"features\":[\"std\",\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.28\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3\",\"optional\":true,\"req\":\"^0.0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3-quinn\",\"optional\":true,\"req\":\"^0.0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"tokio\"],\"name\":\"hickory-resolver\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body\",\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\"],\"name\":\"hyper\",\"req\":\"^1.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"http2\",\"client\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"tls12\"],\"name\":\"hyper-rustls\",\"optional\":true,\"req\":\"^0.27.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"hyper-tls\",\"optional\":true,\"req\":\"^0.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\",\"client-legacy\",\"client-proxy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"http2\",\"client\",\"client-legacy\",\"server-auto\",\"server-graceful\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"req\":\"^0.3.77\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0\"},{\"name\":\"log\",\"req\":\"^0.4.17\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.16\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"^0.2.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.18\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"rustls\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.11.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"std\"],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"^1.9.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde_json\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"},{\"features\":[\"futures\"],\"name\":\"sync_wrapper\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"net\",\"time\"],\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.9\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"retry\",\"timeout\",\"util\"],\"name\":\"tower\",\"req\":\"^0.5.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"limit\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"features\":[\"follow-redirect\"],\"name\":\"tower-http\",\"req\":\"^0.6.5\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tower-service\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"url\",\"req\":\"^2.4\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"serde-serialize\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.18\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-streams\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"AbortController\",\"AbortSignal\",\"Headers\",\"Request\",\"RequestInit\",\"RequestMode\",\"Response\",\"Window\",\"FormData\",\"Blob\",\"BlobPropertyBag\",\"ServiceWorkerGlobalScope\",\"RequestCredentials\",\"File\",\"ReadableStream\",\"RequestCache\"],\"name\":\"web-sys\",\"req\":\"^0.3.28\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"zstd_crate\",\"package\":\"zstd\",\"req\":\"^0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"}],\"features\":{\"__rustls\":[\"dep:hyper-rustls\",\"dep:tokio-rustls\",\"dep:rustls\",\"__tls\"],\"__rustls-ring\":[\"hyper-rustls?/ring\",\"tokio-rustls?/ring\",\"rustls?/ring\",\"quinn?/ring\"],\"__tls\":[\"dep:rustls-pki-types\",\"tokio/io-util\"],\"blocking\":[\"dep:futures-channel\",\"futures-channel?/sink\",\"dep:futures-util\",\"futures-util?/io\",\"futures-util?/sink\",\"tokio/sync\"],\"brotli\":[\"dep:async-compression\",\"async-compression?/brotli\",\"dep:futures-util\",\"dep:tokio-util\"],\"charset\":[\"dep:encoding_rs\",\"dep:mime\"],\"cookies\":[\"dep:cookie_crate\",\"dep:cookie_store\"],\"default\":[\"default-tls\",\"charset\",\"http2\",\"system-proxy\"],\"default-tls\":[\"dep:hyper-tls\",\"dep:native-tls-crate\",\"__tls\",\"dep:tokio-native-tls\"],\"deflate\":[\"dep:async-compression\",\"async-compression?/zlib\",\"dep:futures-util\",\"dep:tokio-util\"],\"gzip\":[\"dep:async-compression\",\"async-compression?/gzip\",\"dep:futures-util\",\"dep:tokio-util\"],\"hickory-dns\":[\"dep:hickory-resolver\",\"dep:once_cell\"],\"http2\":[\"h2\",\"hyper/http2\",\"hyper-util/http2\",\"hyper-rustls?/http2\"],\"http3\":[\"rustls-tls-manual-roots\",\"dep:h3\",\"dep:h3-quinn\",\"dep:quinn\",\"tokio/macros\"],\"json\":[\"dep:serde_json\"],\"macos-system-configuration\":[\"system-proxy\"],\"multipart\":[\"dep:mime_guess\",\"dep:futures-util\"],\"native-tls\":[\"default-tls\"],\"native-tls-alpn\":[\"native-tls\",\"native-tls-crate?/alpn\",\"hyper-tls?/alpn\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate?/vendored\"],\"rustls-tls\":[\"rustls-tls-webpki-roots\"],\"rustls-tls-manual-roots\":[\"rustls-tls-manual-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-manual-roots-no-provider\":[\"__rustls\"],\"rustls-tls-native-roots\":[\"rustls-tls-native-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-native-roots-no-provider\":[\"dep:rustls-native-certs\",\"hyper-rustls?/native-tokio\",\"__rustls\"],\"rustls-tls-no-provider\":[\"rustls-tls-manual-roots-no-provider\"],\"rustls-tls-webpki-roots\":[\"rustls-tls-webpki-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-webpki-roots-no-provider\":[\"dep:webpki-roots\",\"hyper-rustls?/webpki-tokio\",\"__rustls\"],\"socks\":[],\"stream\":[\"tokio/fs\",\"dep:futures-util\",\"dep:tokio-util\",\"dep:wasm-streams\"],\"system-proxy\":[\"hyper-util/client-proxy-system\"],\"trust-dns\":[],\"zstd\":[\"dep:async-compression\",\"async-compression?/zstd\",\"dep:futures-util\",\"dep:tokio-util\"]}}", + "reqwest_0.12.28": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"brotli_crate\",\"package\":\"brotli\",\"req\":\"^8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"cookie_crate\",\"optional\":true,\"package\":\"cookie\",\"req\":\"^0.18.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.22.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.28\"},{\"default_features\":false,\"features\":[\"std\",\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.28\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3\",\"optional\":true,\"req\":\"^0.0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3-quinn\",\"optional\":true,\"req\":\"^0.0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"tokio\"],\"name\":\"hickory-resolver\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body\",\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\"],\"name\":\"hyper\",\"req\":\"^1.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"http2\",\"client\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"tls12\"],\"name\":\"hyper-rustls\",\"optional\":true,\"req\":\"^0.27.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"hyper-tls\",\"optional\":true,\"req\":\"^0.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\",\"client-legacy\",\"client-proxy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"http2\",\"client\",\"client-legacy\",\"server-auto\",\"server-graceful\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"req\":\"^0.3.77\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0\"},{\"name\":\"log\",\"req\":\"^0.4.17\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.16\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"^0.2.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.18\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"rustls\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.11.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"std\"],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"^1.9.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde_json\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"},{\"features\":[\"futures\"],\"name\":\"sync_wrapper\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"net\",\"time\"],\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.9\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"retry\",\"timeout\",\"util\"],\"name\":\"tower\",\"req\":\"^0.5.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"limit\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"features\":[\"follow-redirect\"],\"name\":\"tower-http\",\"req\":\"^0.6.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tower-service\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"url\",\"req\":\"^2.4\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"serde-serialize\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.18\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-streams\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"AbortController\",\"AbortSignal\",\"Headers\",\"Request\",\"RequestInit\",\"RequestMode\",\"Response\",\"Window\",\"FormData\",\"Blob\",\"BlobPropertyBag\",\"ServiceWorkerGlobalScope\",\"RequestCredentials\",\"File\",\"ReadableStream\",\"RequestCache\"],\"name\":\"web-sys\",\"req\":\"^0.3.28\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"zstd_crate\",\"package\":\"zstd\",\"req\":\"^0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"}],\"features\":{\"__rustls\":[\"dep:hyper-rustls\",\"dep:tokio-rustls\",\"dep:rustls\",\"__tls\"],\"__rustls-ring\":[\"hyper-rustls?/ring\",\"tokio-rustls?/ring\",\"rustls?/ring\",\"quinn?/ring\"],\"__tls\":[\"dep:rustls-pki-types\",\"tokio/io-util\"],\"blocking\":[\"dep:futures-channel\",\"futures-channel?/sink\",\"dep:futures-util\",\"futures-util?/io\",\"futures-util?/sink\",\"tokio/sync\"],\"brotli\":[\"tower-http/decompression-br\"],\"charset\":[\"dep:encoding_rs\",\"dep:mime\"],\"cookies\":[\"dep:cookie_crate\",\"dep:cookie_store\"],\"default\":[\"default-tls\",\"charset\",\"http2\",\"system-proxy\"],\"default-tls\":[\"dep:hyper-tls\",\"dep:native-tls-crate\",\"__tls\",\"dep:tokio-native-tls\"],\"deflate\":[\"tower-http/decompression-deflate\"],\"gzip\":[\"tower-http/decompression-gzip\"],\"hickory-dns\":[\"dep:hickory-resolver\",\"dep:once_cell\"],\"http2\":[\"h2\",\"hyper/http2\",\"hyper-util/http2\",\"hyper-rustls?/http2\"],\"http3\":[\"rustls-tls-manual-roots\",\"dep:h3\",\"dep:h3-quinn\",\"dep:quinn\",\"tokio/macros\"],\"json\":[\"dep:serde_json\"],\"macos-system-configuration\":[\"system-proxy\"],\"multipart\":[\"dep:mime_guess\",\"dep:futures-util\"],\"native-tls\":[\"default-tls\"],\"native-tls-alpn\":[\"native-tls\",\"native-tls-crate?/alpn\",\"hyper-tls?/alpn\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate?/vendored\"],\"rustls-tls\":[\"rustls-tls-webpki-roots\"],\"rustls-tls-manual-roots\":[\"rustls-tls-manual-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-manual-roots-no-provider\":[\"__rustls\"],\"rustls-tls-native-roots\":[\"rustls-tls-native-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-native-roots-no-provider\":[\"dep:rustls-native-certs\",\"hyper-rustls?/native-tokio\",\"__rustls\"],\"rustls-tls-no-provider\":[\"rustls-tls-manual-roots-no-provider\"],\"rustls-tls-webpki-roots\":[\"rustls-tls-webpki-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-webpki-roots-no-provider\":[\"dep:webpki-roots\",\"hyper-rustls?/webpki-tokio\",\"__rustls\"],\"socks\":[],\"stream\":[\"tokio/fs\",\"dep:futures-util\",\"dep:tokio-util\",\"dep:wasm-streams\"],\"system-proxy\":[\"hyper-util/client-proxy-system\"],\"trust-dns\":[],\"zstd\":[\"tower-http/decompression-zstd\"]}}", "resolv-conf_0.7.6": "{\"dependencies\":[],\"features\":{\"system\":[]}}", "ring_0.17.14": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.2.8\"},{\"default_features\":false,\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"getrandom\",\"req\":\"^0.2.10\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.148\",\"target\":\"cfg(all(any(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), all(target_arch = \\\"arm\\\", target_endian = \\\"little\\\")), any(target_os = \\\"android\\\", target_os = \\\"linux\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.155\",\"target\":\"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_vendor = \\\"apple\\\", any(target_os = \\\"ios\\\", target_os = \\\"macos\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\")))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.148\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.37\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_os = \\\"windows\\\"))\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\",\"dev_urandom_fallback\"],\"dev_urandom_fallback\":[],\"less-safe-getrandom-custom-or-rdrand\":[],\"less-safe-getrandom-espidf\":[],\"slow_tests\":[],\"std\":[\"alloc\"],\"test_logging\":[],\"unstable-testing-arm-no-hw\":[],\"unstable-testing-arm-no-neon\":[],\"wasm32_unknown_unknown_js\":[\"getrandom/js\"]}}", "rmcp-macros_0.12.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.23\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "rmcp_0.12.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"async-trait\",\"req\":\"^0.1.89\"},{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"serde\",\"clock\",\"std\",\"oldtime\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"serde\"],\"name\":\"chrono\",\"req\":\"^0.4.38\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"http\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"oauth2\",\"optional\":true,\"req\":\"^5.0\"},{\"name\":\"pastey\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"tokio1\"],\"name\":\"process-wrap\",\"optional\":true,\"req\":\"^9.0\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"json\",\"stream\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"rmcp-macros\",\"optional\":true,\"req\":\"^0.12.0\"},{\"features\":[\"chrono04\"],\"name\":\"schemars\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"chrono04\"],\"kind\":\"dev\",\"name\":\"schemars\",\"req\":\"^1.1.0\"},{\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"sse-stream\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"sync\",\"macros\",\"rt\",\"time\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.4\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"__reqwest\":[\"dep:reqwest\"],\"auth\":[\"dep:oauth2\",\"__reqwest\",\"dep:url\"],\"client\":[\"dep:tokio-stream\"],\"client-side-sse\":[\"dep:sse-stream\",\"dep:http\"],\"default\":[\"base64\",\"macros\",\"server\"],\"elicitation\":[],\"macros\":[\"dep:rmcp-macros\",\"dep:pastey\"],\"reqwest\":[\"__reqwest\",\"reqwest?/rustls-tls\"],\"reqwest-tls-no-provider\":[\"__reqwest\",\"reqwest?/rustls-tls-no-provider\"],\"schemars\":[\"dep:schemars\"],\"server\":[\"transport-async-rw\",\"dep:schemars\"],\"server-side-http\":[\"uuid\",\"dep:rand\",\"dep:tokio-stream\",\"dep:http\",\"dep:http-body\",\"dep:http-body-util\",\"dep:bytes\",\"dep:sse-stream\",\"tower\"],\"tower\":[\"dep:tower-service\"],\"transport-async-rw\":[\"tokio/io-util\",\"tokio-util/codec\"],\"transport-child-process\":[\"transport-async-rw\",\"tokio/process\",\"dep:process-wrap\"],\"transport-io\":[\"transport-async-rw\",\"tokio/io-std\"],\"transport-streamable-http-client\":[\"client-side-sse\",\"transport-worker\"],\"transport-streamable-http-client-reqwest\":[\"transport-streamable-http-client\",\"reqwest\"],\"transport-streamable-http-server\":[\"transport-streamable-http-server-session\",\"server-side-http\",\"transport-worker\"],\"transport-streamable-http-server-session\":[\"transport-async-rw\",\"dep:tokio-stream\"],\"transport-worker\":[\"dep:tokio-stream\"]}}", "rsa_0.9.10": "{\"dependencies\":[{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"base64ct\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"const-oid\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"alloc\",\"oid\"],\"name\":\"digest\",\"req\":\"^0.10.5\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.1\"},{\"default_features\":false,\"features\":[\"i128\",\"prime\",\"zeroize\"],\"name\":\"num-bigint\",\"package\":\"num-bigint-dig\",\"req\":\"^0.8.6\"},{\"default_features\":false,\"name\":\"num-integer\",\"req\":\"^0.1.39\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"req\":\"^0.2.9\"},{\"default_features\":false,\"features\":[\"alloc\",\"pkcs8\"],\"name\":\"pkcs1\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"pkcs8\",\"req\":\"^0.10.2\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand_chacha\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.6.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.184\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.89\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10.5\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10.5\"},{\"default_features\":false,\"features\":[\"oid\"],\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10.6\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10.6\"},{\"default_features\":false,\"features\":[\"oid\"],\"kind\":\"dev\",\"name\":\"sha3\",\"req\":\"^0.10.7\"},{\"default_features\":false,\"features\":[\"alloc\",\"digest\",\"rand_core\"],\"name\":\"signature\",\"req\":\">2.0, <2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"spki\",\"req\":\"^0.7.3\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.1.1\"},{\"features\":[\"alloc\"],\"name\":\"zeroize\",\"req\":\"^1.5\"}],\"features\":{\"default\":[\"std\",\"pem\",\"u64_digit\"],\"getrandom\":[\"rand_core/getrandom\"],\"hazmat\":[],\"nightly\":[\"num-bigint/nightly\"],\"pem\":[\"pkcs1/pem\",\"pkcs8/pem\"],\"pkcs5\":[\"pkcs8/encryption\"],\"serde\":[\"dep:serde\",\"num-bigint/serde\"],\"std\":[\"digest/std\",\"pkcs1/std\",\"pkcs8/std\",\"rand_core/std\",\"signature/std\"],\"u64_digit\":[\"num-bigint/u64_digit\"]}}", - "rustc-demangle_0.1.25": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"compiler_builtins\":[],\"rustc-dep-of-std\":[\"core\"],\"std\":[]}}", + "rust-embed-impl_8.11.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"name\":\"rust-embed-utils\",\"req\":\"^8.11.0\"},{\"name\":\"shellexpand\",\"optional\":true,\"req\":\"^3\"},{\"default_features\":false,\"features\":[\"derive\",\"parsing\",\"proc-macro\",\"printing\"],\"name\":\"syn\",\"req\":\"^2\"},{\"name\":\"walkdir\",\"req\":\"^2.3.1\"}],\"features\":{\"compression\":[],\"debug-embed\":[],\"deterministic-timestamps\":[],\"include-exclude\":[\"rust-embed-utils/include-exclude\"],\"interpolate-folder-path\":[\"shellexpand\"],\"mime-guess\":[\"rust-embed-utils/mime-guess\"]}}", + "rust-embed-utils_8.11.0": "{\"dependencies\":[{\"name\":\"globset\",\"optional\":true,\"req\":\"^0.4.8\"},{\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0.4\"},{\"name\":\"sha2\",\"req\":\"^0.10.5\"},{\"name\":\"walkdir\",\"req\":\"^2.3.1\"}],\"features\":{\"debug-embed\":[],\"include-exclude\":[\"globset\"],\"mime-guess\":[\"mime_guess\"]}}", + "rust-embed_8.11.0": "{\"dependencies\":[{\"name\":\"actix-web\",\"optional\":true,\"req\":\"^4\"},{\"default_features\":false,\"features\":[\"http1\",\"tokio\"],\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"hex\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"include-flate\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0.5\"},{\"default_features\":false,\"features\":[\"server\"],\"name\":\"poem\",\"optional\":true,\"req\":\"^1.3.30\"},{\"default_features\":false,\"name\":\"rocket\",\"optional\":true,\"req\":\"^0.5.0-rc.2\"},{\"name\":\"rust-embed-impl\",\"req\":\"^8.9.0\"},{\"name\":\"rust-embed-utils\",\"req\":\"^8.9.0\"},{\"default_features\":false,\"name\":\"salvo\",\"optional\":true,\"req\":\"^0.16\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"default_features\":false,\"name\":\"warp\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"actix\":[\"actix-web\",\"mime_guess\"],\"axum-ex\":[\"axum\",\"tokio\",\"mime_guess\"],\"compression\":[\"rust-embed-impl/compression\",\"include-flate\"],\"debug-embed\":[\"rust-embed-impl/debug-embed\",\"rust-embed-utils/debug-embed\"],\"deterministic-timestamps\":[\"rust-embed-impl/deterministic-timestamps\"],\"include-exclude\":[\"rust-embed-impl/include-exclude\",\"rust-embed-utils/include-exclude\"],\"interpolate-folder-path\":[\"rust-embed-impl/interpolate-folder-path\"],\"mime-guess\":[\"rust-embed-impl/mime-guess\",\"rust-embed-utils/mime-guess\"],\"poem-ex\":[\"poem\",\"tokio\",\"mime_guess\",\"hex\"],\"salvo-ex\":[\"salvo\",\"tokio\",\"mime_guess\",\"hex\"],\"warp-ex\":[\"warp\",\"tokio\",\"mime_guess\"]}}", + "rustc-demangle_0.1.27": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"compiler_builtins\":[],\"rustc-dep-of-std\":[\"core\"],\"std\":[]}}", + "rustc-hash_1.1.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "rustc-hash_2.1.1": "{\"dependencies\":[{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8\"}],\"features\":{\"default\":[\"std\"],\"nightly\":[],\"rand\":[\"dep:rand\",\"std\"],\"std\":[]}}", "rustc_version_0.4.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"semver\",\"req\":\"^1.0\"}],\"features\":{}}", "rustix_0.38.44": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"itoa\",\"optional\":true,\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.161\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.5.2\",\"target\":\"cfg(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"))\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_NetworkManagement_IpHelper\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"procfs\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"cc\":[],\"default\":[\"std\",\"use-libc-auxv\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"linux-raw-sys/io_uring\"],\"libc-extra-traits\":[\"libc?/extra_traits\"],\"linux_4_11\":[],\"linux_latest\":[\"linux_4_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[\"fs\"],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"procfs\":[\"once_cell\",\"itoa\",\"fs\"],\"pty\":[\"itoa\",\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"compiler_builtins\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\",\"compiler_builtins?/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\",\"libc-extra-traits\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\",\"libc-extra-traits\"],\"use-libc-auxv\":[]}}", - "rustix_1.0.8": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.168\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.168\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.168\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.9.2\",\"target\":\"cfg(all(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.9.2\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", - "rustls-native-certs_0.8.1": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.1.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.102\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^0.26\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{}}", - "rustls-pki-types_1.12.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", - "rustls-webpki_0.103.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.9\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"bzip2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.17.2\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"default_features\":false,\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"}],\"features\":{\"alloc\":[\"ring?/alloc\",\"pki-types/alloc\"],\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"aws-lc-rs-unstable\":[\"aws-lc-rs\",\"aws-lc-rs/unstable\"],\"default\":[\"std\"],\"ring\":[\"dep:ring\"],\"std\":[\"alloc\",\"pki-types/std\"]}}", - "rustls_0.23.29": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.4\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.7\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}", - "rustversion_1.0.21": "{\"dependencies\":[{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}", + "rustix_1.1.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"auxvec\",\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", + "rustls-native-certs_0.8.3": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18\"}],\"features\":{}}", + "rustls-pki-types_1.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", + "rustls-webpki_0.103.9": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"bzip2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.17.2\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14.2\"},{\"default_features\":false,\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"}],\"features\":{\"alloc\":[\"ring?/alloc\",\"pki-types/alloc\"],\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"aws-lc-rs-unstable\":[\"aws-lc-rs\",\"aws-lc-rs/unstable\"],\"default\":[\"std\"],\"ring\":[\"dep:ring\"],\"std\":[\"alloc\",\"pki-types/std\"]}}", + "rustls_0.23.36": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.5\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.8\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}", + "rustversion_1.0.22": "{\"dependencies\":[{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}", "rustyline_14.0.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.2\"},{\"name\":\"bitflags\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"buffer-redux\",\"optional\":true,\"req\":\"^1.0\",\"target\":\"cfg(unix)\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"clipboard-win\",\"req\":\"^5.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"fd-lock\",\"optional\":true,\"req\":\"^4.0.0\"},{\"name\":\"home\",\"optional\":true,\"req\":\"^0.5.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"memchr\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"fs\",\"ioctl\",\"poll\",\"signal\",\"term\"],\"name\":\"nix\",\"req\":\"^0.28\",\"target\":\"cfg(unix)\"},{\"name\":\"radix_trie\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.5.5\"},{\"default_features\":false,\"features\":[\"bundled\",\"backup\"],\"name\":\"rusqlite\",\"optional\":true,\"req\":\"^0.31.0\"},{\"name\":\"rustyline-derive\",\"optional\":true,\"req\":\"^0.10.0\"},{\"default_features\":false,\"name\":\"signal-hook\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"name\":\"skim\",\"optional\":true,\"req\":\"^0.10\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"},{\"name\":\"termios\",\"optional\":true,\"req\":\"^0.3.3\",\"target\":\"cfg(unix)\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.0\"},{\"name\":\"unicode-width\",\"req\":\"^0.1\"},{\"name\":\"utf8parse\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\",\"Win32_Security\",\"Win32_System_Threading\",\"Win32_UI_Input_KeyboardAndMouse\"],\"name\":\"windows-sys\",\"req\":\"^0.52.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"case_insensitive_history_search\":[\"regex\"],\"custom-bindings\":[\"radix_trie\"],\"default\":[\"custom-bindings\",\"with-dirs\",\"with-file-history\"],\"derive\":[\"rustyline-derive\"],\"with-dirs\":[\"home\"],\"with-file-history\":[\"fd-lock\"],\"with-fuzzy\":[\"skim\"],\"with-sqlite-history\":[\"rusqlite\"]}}", - "ryu_1.0.20": "{\"dependencies\":[{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.4\"}],\"features\":{\"small\":[]}}", + "ryu_1.0.22": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.4\"}],\"features\":{\"small\":[]}}", + "salsa20_0.10.2": "{\"dependencies\":[{\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"cipher\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3.3\"}],\"features\":{\"std\":[\"cipher/std\"],\"zeroize\":[\"cipher/zeroize\"]}}", "same-file_1.0.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"winapi-util\",\"req\":\"^0.1.1\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "scc_2.4.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"name\":\"equivalent\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"name\":\"sdd\",\"req\":\"^3.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.47\"}],\"features\":{\"loom\":[\"dep:loom\",\"sdd/loom\"]}}", "schannel_0.1.28": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_Security_Cryptography\",\"Win32_Security_Authentication_Identity\",\"Win32_Security_Credentials\",\"Win32_System_LibraryLoader\",\"Win32_System_Memory\",\"Win32_System_SystemInformation\"],\"name\":\"windows-sys\",\"req\":\"^0.61\"},{\"features\":[\"Win32_System_SystemInformation\",\"Win32_System_Time\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\"}],\"features\":{}}", @@ -1090,64 +1139,68 @@ "schemafy_lib_0.5.2": "{\"dependencies\":[{\"name\":\"Inflector\",\"req\":\"^0.11\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"schemafy_core\",\"req\":\"^0.5.2\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^1.0\"}],\"features\":{}}", "schemars_0.8.22": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec05\",\"optional\":true,\"package\":\"arrayvec\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"arrayvec07\",\"optional\":true,\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"bigdecimal03\",\"optional\":true,\"package\":\"bigdecimal\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"bigdecimal04\",\"optional\":true,\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"dyn-clone\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"either\",\"optional\":true,\"req\":\"^1.3\"},{\"name\":\"enumset\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"serde-1\"],\"name\":\"indexmap\",\"optional\":true,\"req\":\"^1.2\"},{\"features\":[\"serde\"],\"name\":\"indexmap2\",\"optional\":true,\"package\":\"indexmap\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"default_features\":false,\"name\":\"rust_decimal\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"schemars_derive\",\"optional\":true,\"req\":\"=0.8.22\"},{\"features\":[\"serde\"],\"name\":\"semver\",\"optional\":true,\"req\":\"^1.0.9\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0.25\"},{\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"smol_str\",\"optional\":true,\"req\":\"^0.1.17\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"url\",\"optional\":true,\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"uuid08\",\"optional\":true,\"package\":\"uuid\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"uuid1\",\"optional\":true,\"package\":\"uuid\",\"req\":\"^1.0\"}],\"features\":{\"arrayvec\":[\"arrayvec05\"],\"bigdecimal\":[\"bigdecimal03\"],\"default\":[\"derive\"],\"derive\":[\"schemars_derive\"],\"derive_json_schema\":[\"impl_json_schema\"],\"impl_json_schema\":[\"derive\"],\"indexmap1\":[\"indexmap\"],\"preserve_order\":[\"indexmap\"],\"raw_value\":[\"serde_json/raw_value\"],\"ui_test\":[],\"uuid\":[\"uuid08\"]}}", "schemars_0.9.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec07\",\"optional\":true,\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"arrayvec07\",\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"bigdecimal04\",\"optional\":true,\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"bigdecimal04\",\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"bytes1\",\"optional\":true,\"package\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"bytes1\",\"package\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"chrono04\",\"optional\":true,\"package\":\"chrono\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono04\",\"package\":\"chrono\",\"req\":\"^0.4\"},{\"name\":\"dyn-clone\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"either1\",\"optional\":true,\"package\":\"either\",\"req\":\"^1.3\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"either1\",\"package\":\"either\",\"req\":\"^1.3\"},{\"features\":[\"derive\",\"email\",\"regex\",\"url\"],\"kind\":\"dev\",\"name\":\"garde\",\"req\":\"^0.22\"},{\"default_features\":false,\"name\":\"indexmap2\",\"optional\":true,\"package\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"indexmap2\",\"package\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"jiff02\",\"optional\":true,\"package\":\"jiff\",\"req\":\"^0.2\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"jiff02\",\"package\":\"jiff\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"jsonschema\",\"req\":\"^0.30\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"ref-cast\",\"req\":\"^1.0.22\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.6\"},{\"default_features\":false,\"name\":\"rust_decimal1\",\"optional\":true,\"package\":\"rust_decimal\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"rust_decimal1\",\"package\":\"rust_decimal\",\"req\":\"^1\"},{\"name\":\"schemars_derive\",\"optional\":true,\"req\":\"=0.9.0\"},{\"default_features\":false,\"name\":\"semver1\",\"optional\":true,\"package\":\"semver\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"semver1\",\"package\":\"semver\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_json\",\"req\":\"^1.0.127\"},{\"kind\":\"dev\",\"name\":\"serde_repr\",\"req\":\"^0.1.19\"},{\"default_features\":false,\"name\":\"smallvec1\",\"optional\":true,\"package\":\"smallvec\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smallvec1\",\"package\":\"smallvec\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"smol_str02\",\"optional\":true,\"package\":\"smol_str\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smol_str02\",\"package\":\"smol_str\",\"req\":\"^0.2.1\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.17\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"url2\",\"optional\":true,\"package\":\"url\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"serde\",\"std\"],\"kind\":\"dev\",\"name\":\"url2\",\"package\":\"url\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"uuid1\",\"optional\":true,\"package\":\"uuid\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"uuid1\",\"package\":\"uuid\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"validator\",\"req\":\"^0.20\"}],\"features\":{\"_ui_test\":[],\"default\":[\"derive\",\"std\"],\"derive\":[\"schemars_derive\"],\"preserve_order\":[\"serde_json/preserve_order\"],\"raw_value\":[\"serde_json/raw_value\"],\"std\":[]}}", - "schemars_1.0.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec07\",\"optional\":true,\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"arrayvec07\",\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"bigdecimal04\",\"optional\":true,\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"bigdecimal04\",\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"bytes1\",\"optional\":true,\"package\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"bytes1\",\"package\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"chrono04\",\"optional\":true,\"package\":\"chrono\",\"req\":\"^0.4.39\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono04\",\"package\":\"chrono\",\"req\":\"^0.4\"},{\"name\":\"dyn-clone\",\"req\":\"^1.0.17\"},{\"default_features\":false,\"name\":\"either1\",\"optional\":true,\"package\":\"either\",\"req\":\"^1.3\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"either1\",\"package\":\"either\",\"req\":\"^1.3\"},{\"features\":[\"derive\",\"email\",\"regex\",\"url\"],\"kind\":\"dev\",\"name\":\"garde\",\"req\":\"^0.22\"},{\"default_features\":false,\"name\":\"indexmap2\",\"optional\":true,\"package\":\"indexmap\",\"req\":\"^2.2.3\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"indexmap2\",\"package\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"jiff02\",\"optional\":true,\"package\":\"jiff\",\"req\":\"^0.2\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"jiff02\",\"package\":\"jiff\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"jsonschema\",\"req\":\"^0.30\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"ref-cast\",\"req\":\"^1.0.22\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.6\"},{\"default_features\":false,\"name\":\"rust_decimal1\",\"optional\":true,\"package\":\"rust_decimal\",\"req\":\"^1.13\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"rust_decimal1\",\"package\":\"rust_decimal\",\"req\":\"^1\"},{\"name\":\"schemars_derive\",\"optional\":true,\"req\":\"=1.0.4\"},{\"default_features\":false,\"name\":\"semver1\",\"optional\":true,\"package\":\"semver\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"semver1\",\"package\":\"semver\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_json\",\"req\":\"^1.0.127\"},{\"kind\":\"dev\",\"name\":\"serde_repr\",\"req\":\"^0.1.19\"},{\"default_features\":false,\"name\":\"smallvec1\",\"optional\":true,\"package\":\"smallvec\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smallvec1\",\"package\":\"smallvec\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"smol_str02\",\"optional\":true,\"package\":\"smol_str\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smol_str02\",\"package\":\"smol_str\",\"req\":\"^0.2.1\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.17\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"url2\",\"optional\":true,\"package\":\"url\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"serde\",\"std\"],\"kind\":\"dev\",\"name\":\"url2\",\"package\":\"url\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"uuid1\",\"optional\":true,\"package\":\"uuid\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"uuid1\",\"package\":\"uuid\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"validator\",\"req\":\"^0.20\"}],\"features\":{\"_ui_test\":[],\"default\":[\"derive\",\"std\"],\"derive\":[\"schemars_derive\"],\"preserve_order\":[\"serde_json/preserve_order\"],\"raw_value\":[\"serde_json/raw_value\"],\"std\":[]}}", + "schemars_1.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"arrayvec07\",\"optional\":true,\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"arrayvec07\",\"package\":\"arrayvec\",\"req\":\"^0.7\"},{\"default_features\":false,\"name\":\"bigdecimal04\",\"optional\":true,\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"bigdecimal04\",\"package\":\"bigdecimal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"bytes1\",\"optional\":true,\"package\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"bytes1\",\"package\":\"bytes\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"chrono04\",\"optional\":true,\"package\":\"chrono\",\"req\":\"^0.4.39\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono04\",\"package\":\"chrono\",\"req\":\"^0.4\"},{\"name\":\"dyn-clone\",\"req\":\"^1.0.17\"},{\"default_features\":false,\"name\":\"either1\",\"optional\":true,\"package\":\"either\",\"req\":\"^1.3\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"either1\",\"package\":\"either\",\"req\":\"^1.3\"},{\"features\":[\"derive\",\"email\",\"regex\",\"url\"],\"kind\":\"dev\",\"name\":\"garde\",\"req\":\"^0.22\"},{\"default_features\":false,\"name\":\"indexmap2\",\"optional\":true,\"package\":\"indexmap\",\"req\":\"^2.2.3\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"indexmap2\",\"package\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"jiff02\",\"optional\":true,\"package\":\"jiff\",\"req\":\"^0.2\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"jiff02\",\"package\":\"jiff\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"jsonschema\",\"req\":\"^0.30\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"ref-cast\",\"req\":\"^1.0.22\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.6\"},{\"default_features\":false,\"name\":\"rust_decimal1\",\"optional\":true,\"package\":\"rust_decimal\",\"req\":\"^1.13\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"rust_decimal1\",\"package\":\"rust_decimal\",\"req\":\"^1\"},{\"name\":\"schemars_derive\",\"optional\":true,\"req\":\"=1.2.1\"},{\"default_features\":false,\"name\":\"semver1\",\"optional\":true,\"package\":\"semver\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"semver1\",\"package\":\"semver\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_json\",\"req\":\"^1.0.127\"},{\"kind\":\"dev\",\"name\":\"serde_repr\",\"req\":\"^0.1.19\"},{\"default_features\":false,\"name\":\"smallvec1\",\"optional\":true,\"package\":\"smallvec\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smallvec1\",\"package\":\"smallvec\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"smol_str02\",\"optional\":true,\"package\":\"smol_str\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smol_str02\",\"package\":\"smol_str\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"smol_str03\",\"optional\":true,\"package\":\"smol_str\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"smol_str03\",\"package\":\"smol_str\",\"req\":\"^0.3.2\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.17\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"url2\",\"optional\":true,\"package\":\"url\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"serde\",\"std\"],\"kind\":\"dev\",\"name\":\"url2\",\"package\":\"url\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"uuid1\",\"optional\":true,\"package\":\"uuid\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"uuid1\",\"package\":\"uuid\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"validator\",\"req\":\"^0.20\"}],\"features\":{\"_ui_test\":[],\"default\":[\"derive\",\"std\"],\"derive\":[\"schemars_derive\"],\"preserve_order\":[\"serde_json/preserve_order\"],\"raw_value\":[\"serde_json/raw_value\"],\"std\":[]}}", "schemars_derive_0.8.22": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"serde_derive_internals\",\"req\":\"^0.29\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", - "schemars_derive_1.0.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"serde_derive_internals\",\"req\":\"^0.29.1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"extra-traits\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", + "schemars_derive_1.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"serde_derive_internals\",\"req\":\"^0.29.1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"extra-traits\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "scoped-tls_1.0.1": "{\"dependencies\":[],\"features\":{}}", "scopeguard_1.2.0": "{\"dependencies\":[],\"features\":{\"default\":[\"use_std\"],\"use_std\":[]}}", + "scrypt_0.11.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"rand_core\"],\"kind\":\"dev\",\"name\":\"password-hash\",\"req\":\"^0.5\"},{\"name\":\"pbkdf2\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"salsa20\",\"req\":\"^0.10.2\"},{\"default_features\":false,\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"simple\",\"std\"],\"simple\":[\"password-hash\"],\"std\":[\"password-hash/std\"]}}", "sdd_3.0.10": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.6\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{}}", "seccompiler_0.5.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.153\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.27\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.9\"}],\"features\":{\"json\":[\"serde\",\"serde_json\"]}}", + "secrecy_0.10.3": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"zeroize\",\"req\":\"^1.6\"}],\"features\":{}}", "secret-service_4.0.0": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"block-padding\",\"alloc\"],\"name\":\"cbc\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"generic-array\",\"req\":\"^0.14\"},{\"name\":\"hkdf\",\"optional\":true,\"req\":\"^0.12.0\"},{\"name\":\"num\",\"req\":\"^0.4.0\"},{\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"openssl\",\"optional\":true,\"req\":\"^0.10.40\"},{\"name\":\"rand\",\"req\":\"^0.8.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.103\"},{\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"test-with\",\"req\":\"^0.8\"},{\"features\":[\"rt\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"zbus\",\"req\":\"^4\"}],\"features\":{\"crypto-openssl\":[\"dep:openssl\"],\"crypto-rust\":[\"dep:aes\",\"dep:cbc\",\"dep:sha2\",\"dep:hkdf\"],\"rt-async-io-crypto-openssl\":[\"zbus/async-io\",\"crypto-openssl\"],\"rt-async-io-crypto-rust\":[\"zbus/async-io\",\"crypto-rust\"],\"rt-tokio-crypto-openssl\":[\"zbus/tokio\",\"crypto-openssl\"],\"rt-tokio-crypto-rust\":[\"zbus/tokio\",\"crypto-rust\"]}}", "security-framework-sys_2.15.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"name\":\"libc\",\"req\":\"^0.2.150\"}],\"features\":{\"OSX_10_10\":[\"OSX_10_9\"],\"OSX_10_11\":[\"OSX_10_10\"],\"OSX_10_12\":[\"OSX_10_11\"],\"OSX_10_13\":[\"OSX_10_12\"],\"OSX_10_14\":[\"OSX_10_13\"],\"OSX_10_15\":[\"OSX_10_14\"],\"OSX_10_9\":[],\"default\":[\"OSX_10_12\"]}}", "security-framework_2.11.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6\"},{\"name\":\"core-foundation\",\"req\":\"^0.9.4\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"libc\",\"req\":\"^0.2.139\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4.6\"},{\"default_features\":false,\"name\":\"security-framework-sys\",\"req\":\"^2.11.1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.3.0\"},{\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{\"OSX_10_10\":[\"OSX_10_9\",\"security-framework-sys/OSX_10_10\"],\"OSX_10_11\":[\"OSX_10_10\",\"security-framework-sys/OSX_10_11\"],\"OSX_10_12\":[\"OSX_10_11\",\"security-framework-sys/OSX_10_12\"],\"OSX_10_13\":[\"OSX_10_12\",\"security-framework-sys/OSX_10_13\",\"alpn\",\"session-tickets\",\"serial-number-bigint\"],\"OSX_10_14\":[\"OSX_10_13\",\"security-framework-sys/OSX_10_14\"],\"OSX_10_15\":[\"OSX_10_14\",\"security-framework-sys/OSX_10_15\"],\"OSX_10_9\":[\"security-framework-sys/OSX_10_9\"],\"alpn\":[],\"default\":[\"OSX_10_12\"],\"job-bless\":[],\"nightly\":[],\"serial-number-bigint\":[\"dep:num-bigint\"],\"session-tickets\":[]}}", "security-framework_3.5.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6\"},{\"name\":\"core-foundation\",\"req\":\"^0.10\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"libc\",\"req\":\"^0.2.139\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"name\":\"security-framework-sys\",\"req\":\"^2.15\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.12.0\"},{\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.23\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{\"OSX_10_12\":[\"security-framework-sys/OSX_10_12\"],\"OSX_10_13\":[\"OSX_10_12\",\"security-framework-sys/OSX_10_13\",\"alpn\",\"session-tickets\"],\"OSX_10_14\":[\"OSX_10_13\",\"security-framework-sys/OSX_10_14\"],\"OSX_10_15\":[\"OSX_10_14\",\"security-framework-sys/OSX_10_15\"],\"alpn\":[],\"default\":[\"OSX_10_12\"],\"job-bless\":[],\"nightly\":[],\"session-tickets\":[],\"sync-keychain\":[\"OSX_10_13\"]}}", + "self_cell_0.10.3": "{\"dependencies\":[{\"name\":\"new_self_cell\",\"package\":\"self_cell\",\"req\":\"^1\"}],\"features\":{\"old_rust\":[\"new_self_cell/old_rust\"]}}", + "self_cell_1.2.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"=1.1.0\"},{\"name\":\"rustversion\",\"optional\":true,\"req\":\">=1\"}],\"features\":{\"old_rust\":[\"rustversion\"]}}", "semver_1.0.27": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"package\":\"serde_core\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde\"],\"std\":[]}}", - "sentry-actix_0.46.0": "{\"dependencies\":[{\"name\":\"actix-http\",\"req\":\"^3.10\"},{\"default_features\":false,\"name\":\"actix-web\",\"req\":\"^4\"},{\"kind\":\"dev\",\"name\":\"actix-web\",\"req\":\"^4\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3.5\"},{\"default_features\":false,\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"}],\"features\":{\"default\":[\"release-health\"],\"release-health\":[\"sentry-core/release-health\"]}}", - "sentry-backtrace_0.46.0": "{\"dependencies\":[{\"name\":\"backtrace\",\"req\":\"^0.3.44\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-perl\"],\"name\":\"regex\",\"req\":\"^1.5.5\"},{\"name\":\"sentry-core\",\"req\":\"^0.46.0\"}],\"features\":{}}", - "sentry-contexts_0.46.0": "{\"dependencies\":[{\"name\":\"hostname\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2.66\"},{\"name\":\"os_info\",\"req\":\"^3.5.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"name\":\"sentry-core\",\"req\":\"^0.46.0\"},{\"name\":\"uname\",\"req\":\"^0.1.1\",\"target\":\"cfg(not(windows))\"}],\"features\":{}}", - "sentry-core_0.46.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.30\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.24\"},{\"features\":[\"std\"],\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.5.3\"},{\"name\":\"sentry-types\",\"req\":\"^0.46.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.104\"},{\"name\":\"serde_json\",\"req\":\"^1.0.46\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^2.0.12\"},{\"features\":[\"rt\",\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"},{\"name\":\"url\",\"req\":\"^2.1.1\"},{\"features\":[\"v4\",\"serde\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"client\":[\"rand\"],\"default\":[],\"logs\":[],\"release-health\":[],\"test\":[\"client\",\"release-health\"]}}", - "sentry-debug-images_0.46.0": "{\"dependencies\":[{\"name\":\"findshlibs\",\"req\":\"=0.10.2\"},{\"name\":\"sentry-core\",\"req\":\"^0.46.0\"}],\"features\":{}}", - "sentry-panic_0.46.0": "{\"dependencies\":[{\"name\":\"sentry-backtrace\",\"req\":\"^0.46.0\"},{\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.0\"}],\"features\":{}}", - "sentry-tracing_0.46.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.9.4\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"sentry-backtrace\",\"optional\":true,\"req\":\"^0.46.0\"},{\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"tracing-core\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.20\"},{\"features\":[\"fmt\",\"registry\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.20\"}],\"features\":{\"backtrace\":[\"dep:sentry-backtrace\"],\"default\":[],\"logs\":[\"sentry-core/logs\"]}}", - "sentry-types_0.46.0": "{\"dependencies\":[{\"features\":[\"serde\"],\"name\":\"debugid\",\"req\":\"^0.8.0\"},{\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.25.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.104\"},{\"name\":\"serde_json\",\"req\":\"^1.0.46\"},{\"name\":\"thiserror\",\"req\":\"^2.0.12\"},{\"features\":[\"formatting\",\"parsing\"],\"name\":\"time\",\"req\":\"^0.3.5\"},{\"features\":[\"serde\"],\"name\":\"url\",\"req\":\"^2.1.1\"},{\"features\":[\"serde\"],\"name\":\"uuid\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"protocol\"],\"protocol\":[]}}", - "sentry_0.46.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"actix-web\",\"req\":\"^4\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.30\"},{\"name\":\"curl\",\"optional\":true,\"req\":\"^0.4.25\"},{\"name\":\"embedded-svc\",\"optional\":true,\"req\":\"^0.28.1\"},{\"name\":\"esp-idf-svc\",\"optional\":true,\"req\":\"^0.51.0\",\"target\":\"cfg(target_os = \\\"espidf\\\")\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0.0\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"name\":\"native-tls\",\"optional\":true,\"req\":\"^0.2.8\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"features\":[\"blocking\",\"json\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.18\"},{\"default_features\":false,\"name\":\"sentry-actix\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-anyhow\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-backtrace\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-contexts\",\"optional\":true,\"req\":\"^0.46.0\"},{\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.0\"},{\"name\":\"sentry-debug-images\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-log\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-opentelemetry\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-panic\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-slog\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-tower\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"sentry-tracing\",\"optional\":true,\"req\":\"^0.46.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.48\"},{\"kind\":\"dev\",\"name\":\"slog\",\"req\":\"^2.5.2\"},{\"features\":[\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.44\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"},{\"features\":[\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"fmt\",\"tracing-log\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"ureq\",\"optional\":true,\"req\":\"^3.0.11\"}],\"features\":{\"actix\":[\"sentry-actix\"],\"anyhow\":[\"sentry-anyhow\"],\"backtrace\":[\"sentry-backtrace\",\"sentry-tracing?/backtrace\"],\"contexts\":[\"sentry-contexts\"],\"curl\":[\"dep:curl\",\"httpdate\"],\"debug-images\":[\"sentry-debug-images\"],\"default\":[\"backtrace\",\"contexts\",\"debug-images\",\"panic\",\"transport\",\"release-health\"],\"embedded-svc-http\":[\"dep:embedded-svc\",\"dep:esp-idf-svc\"],\"log\":[\"sentry-log\"],\"logs\":[\"sentry-core/logs\",\"sentry-tracing?/logs\",\"sentry-log?/logs\"],\"native-tls\":[\"dep:native-tls\",\"reqwest?/default-tls\",\"ureq?/native-tls\"],\"opentelemetry\":[\"sentry-opentelemetry\"],\"panic\":[\"sentry-panic\"],\"release-health\":[\"sentry-core/release-health\",\"sentry-actix?/release-health\"],\"reqwest\":[\"dep:reqwest\",\"httpdate\",\"tokio\"],\"rustls\":[\"dep:rustls\",\"reqwest?/rustls-tls\",\"ureq?/rustls\"],\"slog\":[\"sentry-slog\"],\"test\":[\"sentry-core/test\"],\"tower\":[\"sentry-tower\"],\"tower-axum-matched-path\":[\"tower-http\",\"sentry-tower/axum-matched-path\"],\"tower-http\":[\"tower\",\"sentry-tower/http\"],\"tracing\":[\"sentry-tracing\"],\"transport\":[\"reqwest\",\"native-tls\"],\"ureq\":[\"dep:ureq\",\"httpdate\"]}}", + "sentry-actix_0.46.1": "{\"dependencies\":[{\"name\":\"actix-http\",\"req\":\"^3.10\"},{\"default_features\":false,\"name\":\"actix-web\",\"req\":\"^4\"},{\"kind\":\"dev\",\"name\":\"actix-web\",\"req\":\"^4\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3.5\"},{\"default_features\":false,\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"}],\"features\":{\"default\":[\"release-health\"],\"release-health\":[\"sentry-core/release-health\"]}}", + "sentry-backtrace_0.46.1": "{\"dependencies\":[{\"name\":\"backtrace\",\"req\":\"^0.3.44\"},{\"default_features\":false,\"features\":[\"std\",\"unicode-perl\"],\"name\":\"regex\",\"req\":\"^1.5.5\"},{\"name\":\"sentry-core\",\"req\":\"^0.46.1\"}],\"features\":{}}", + "sentry-contexts_0.46.1": "{\"dependencies\":[{\"name\":\"hostname\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"req\":\"^0.2.66\"},{\"name\":\"os_info\",\"req\":\"^3.5.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"name\":\"sentry-core\",\"req\":\"^0.46.1\"},{\"name\":\"uname\",\"req\":\"^0.1.1\",\"target\":\"cfg(not(windows))\"}],\"features\":{}}", + "sentry-core_0.46.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.30\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.24\"},{\"features\":[\"std\"],\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.5.3\"},{\"name\":\"sentry-types\",\"req\":\"^0.46.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.104\"},{\"name\":\"serde_json\",\"req\":\"^1.0.46\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^2.0.12\"},{\"features\":[\"rt\",\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"},{\"name\":\"url\",\"req\":\"^2.1.1\"},{\"features\":[\"v4\",\"serde\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"client\":[\"rand\"],\"default\":[],\"logs\":[],\"release-health\":[],\"test\":[\"client\",\"release-health\"]}}", + "sentry-debug-images_0.46.1": "{\"dependencies\":[{\"name\":\"findshlibs\",\"req\":\"=0.10.2\"},{\"name\":\"sentry-core\",\"req\":\"^0.46.1\"}],\"features\":{}}", + "sentry-panic_0.46.1": "{\"dependencies\":[{\"name\":\"sentry-backtrace\",\"req\":\"^0.46.1\"},{\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.1\"}],\"features\":{}}", + "sentry-tracing_0.46.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.9.4\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"sentry-backtrace\",\"optional\":true,\"req\":\"^0.46.1\"},{\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"tracing-core\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.20\"},{\"features\":[\"fmt\",\"registry\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.20\"}],\"features\":{\"backtrace\":[\"dep:sentry-backtrace\"],\"default\":[],\"logs\":[\"sentry-core/logs\"]}}", + "sentry-types_0.46.1": "{\"dependencies\":[{\"features\":[\"serde\"],\"name\":\"debugid\",\"req\":\"^0.8.0\"},{\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.25.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.104\"},{\"name\":\"serde_json\",\"req\":\"^1.0.46\"},{\"name\":\"thiserror\",\"req\":\"^2.0.12\"},{\"features\":[\"formatting\",\"parsing\"],\"name\":\"time\",\"req\":\"^0.3.5\"},{\"features\":[\"serde\"],\"name\":\"url\",\"req\":\"^2.1.1\"},{\"features\":[\"serde\"],\"name\":\"uuid\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"protocol\"],\"protocol\":[]}}", + "sentry_0.46.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"actix-web\",\"req\":\"^4\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.30\"},{\"name\":\"curl\",\"optional\":true,\"req\":\"^0.4.25\"},{\"name\":\"embedded-svc\",\"optional\":true,\"req\":\"^0.28.1\"},{\"name\":\"esp-idf-svc\",\"optional\":true,\"req\":\"^0.51.0\",\"target\":\"cfg(target_os = \\\"espidf\\\")\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0.0\"},{\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"name\":\"native-tls\",\"optional\":true,\"req\":\"^0.2.8\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"features\":[\"blocking\",\"json\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.12.25\"},{\"default_features\":false,\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.18\"},{\"default_features\":false,\"name\":\"sentry-actix\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-anyhow\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-backtrace\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-contexts\",\"optional\":true,\"req\":\"^0.46.1\"},{\"features\":[\"client\"],\"name\":\"sentry-core\",\"req\":\"^0.46.1\"},{\"name\":\"sentry-debug-images\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-log\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-opentelemetry\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-panic\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-slog\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-tower\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"sentry-tracing\",\"optional\":true,\"req\":\"^0.46.1\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.48\"},{\"kind\":\"dev\",\"name\":\"slog\",\"req\":\"^2.5.2\"},{\"features\":[\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.44\"},{\"features\":[\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.44\"},{\"features\":[\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"fmt\",\"tracing-log\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"ureq\",\"optional\":true,\"req\":\"^3.0.11\"}],\"features\":{\"actix\":[\"sentry-actix\"],\"anyhow\":[\"sentry-anyhow\"],\"backtrace\":[\"sentry-backtrace\",\"sentry-tracing?/backtrace\"],\"contexts\":[\"sentry-contexts\"],\"curl\":[\"dep:curl\",\"httpdate\"],\"debug-images\":[\"sentry-debug-images\"],\"default\":[\"backtrace\",\"contexts\",\"debug-images\",\"panic\",\"transport\",\"release-health\"],\"embedded-svc-http\":[\"dep:embedded-svc\",\"dep:esp-idf-svc\"],\"log\":[\"sentry-log\"],\"logs\":[\"sentry-core/logs\",\"sentry-tracing?/logs\",\"sentry-log?/logs\"],\"native-tls\":[\"dep:native-tls\",\"reqwest?/default-tls\",\"ureq?/native-tls\"],\"opentelemetry\":[\"sentry-opentelemetry\"],\"panic\":[\"sentry-panic\"],\"release-health\":[\"sentry-core/release-health\",\"sentry-actix?/release-health\"],\"reqwest\":[\"dep:reqwest\",\"httpdate\",\"tokio\"],\"rustls\":[\"dep:rustls\",\"reqwest?/rustls-tls\",\"ureq?/rustls\"],\"slog\":[\"sentry-slog\"],\"test\":[\"sentry-core/test\"],\"tower\":[\"sentry-tower\"],\"tower-axum-matched-path\":[\"tower-http\",\"sentry-tower/axum-matched-path\"],\"tower-http\":[\"tower\",\"sentry-tower/http\"],\"tracing\":[\"sentry-tracing\"],\"transport\":[\"reqwest\",\"native-tls\"],\"ureq\":[\"dep:ureq\",\"httpdate\"]}}", "serde_1.0.228": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"result\"],\"name\":\"serde_core\",\"req\":\"=1.0.228\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"serde_core/alloc\"],\"default\":[\"std\"],\"derive\":[\"serde_derive\"],\"rc\":[\"serde_core/rc\"],\"std\":[\"serde_core/std\"],\"unstable\":[\"serde_core/unstable\"]}}", "serde_core_1.0.228": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"name\":\"serde_derive\",\"req\":\"=1.0.228\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"result\"],\"rc\":[],\"result\":[],\"std\":[],\"unstable\":[]}}", "serde_derive_1.0.228": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0.81\"}],\"features\":{\"default\":[],\"deserialize_in_place\":[]}}", "serde_derive_internals_0.29.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"default_features\":false,\"features\":[\"clone-impls\",\"derive\",\"parsing\",\"printing\"],\"name\":\"syn\",\"req\":\"^2.0.46\"}],\"features\":{}}", "serde_html_form_0.3.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches2\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"divan\",\"req\":\"^0.1.11\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"form_urlencoded\",\"req\":\"^1.0.1\"},{\"default_features\":false,\"name\":\"indexmap\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.45.0\"},{\"name\":\"itoa\",\"req\":\"^1.0.1\"},{\"name\":\"ryu\",\"optional\":true,\"req\":\"^1.0.9\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"}],\"features\":{\"default\":[\"ryu\",\"std\"],\"std\":[]}}", - "serde_json_1.0.145": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.11\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.2.3\"},{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2.0.2\"},{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"name\":\"ryu\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11.10\"},{\"default_features\":false,\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_stacker\",\"req\":\"^0.1.8\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"alloc\":[\"serde_core/alloc\"],\"arbitrary_precision\":[],\"default\":[\"std\"],\"float_roundtrip\":[],\"preserve_order\":[\"indexmap\",\"std\"],\"raw_value\":[],\"std\":[\"memchr/std\",\"serde_core/std\"],\"unbounded_depth\":[]}}", + "serde_json_1.0.149": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.11\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.2.3\"},{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2.0.2\"},{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.194\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11.10\"},{\"default_features\":false,\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_stacker\",\"req\":\"^0.1.8\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"},{\"name\":\"zmij\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[\"serde_core/alloc\"],\"arbitrary_precision\":[],\"default\":[\"std\"],\"float_roundtrip\":[],\"preserve_order\":[\"indexmap\",\"std\"],\"raw_value\":[],\"std\":[\"memchr/std\",\"serde_core/std\"],\"unbounded_depth\":[]}}", "serde_path_to_error_0.1.20": "{\"dependencies\":[{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.100\"}],\"features\":{}}", "serde_repr_0.1.20": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.166\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.100\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.81\"}],\"features\":{}}", - "serde_spanned_1.0.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.145\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"default\":[\"std\",\"serde\"],\"serde\":[\"dep:serde\"],\"std\":[\"alloc\",\"serde?/std\"]}}", + "serde_spanned_1.0.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\"],\"default\":[\"std\",\"serde\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"alloc\",\"serde_core?/std\"]}}", "serde_urlencoded_0.7.1": "{\"dependencies\":[{\"name\":\"form_urlencoded\",\"req\":\"^1\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"name\":\"ryu\",\"req\":\"^1\"},{\"name\":\"serde\",\"req\":\"^1.0.69\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"}],\"features\":{}}", "serde_with_3.16.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22.1\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"chrono_0_4\",\"optional\":true,\"package\":\"chrono\",\"req\":\"^0.4.20\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"expect-test\",\"req\":\"^1.5.1\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.3\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"hashbrown_0_14\",\"optional\":true,\"package\":\"hashbrown\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"hashbrown_0_15\",\"optional\":true,\"package\":\"hashbrown\",\"req\":\"^0.15.0\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"hashbrown_0_16\",\"optional\":true,\"package\":\"hashbrown\",\"req\":\"^0.16.0\"},{\"default_features\":false,\"name\":\"hex\",\"optional\":true,\"req\":\"^0.4.3\"},{\"default_features\":false,\"features\":[\"serde-1\"],\"name\":\"indexmap_1\",\"optional\":true,\"package\":\"indexmap\",\"req\":\"^1.8\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"indexmap_2\",\"optional\":true,\"package\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"resolve-file\"],\"kind\":\"dev\",\"name\":\"jsonschema\",\"req\":\"^0.33.0\"},{\"kind\":\"dev\",\"name\":\"mime\",\"req\":\"^0.3.16\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.12.1\"},{\"kind\":\"dev\",\"name\":\"rmp-serde\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"ron\",\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.22\"},{\"default_features\":false,\"name\":\"schemars_0_8\",\"optional\":true,\"package\":\"schemars\",\"req\":\"^0.8.16\"},{\"kind\":\"dev\",\"name\":\"schemars_0_8\",\"package\":\"schemars\",\"req\":\"^0.8.16\"},{\"default_features\":false,\"name\":\"schemars_0_9\",\"optional\":true,\"package\":\"schemars\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"schemars_0_9\",\"package\":\"schemars\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"schemars_1\",\"optional\":true,\"package\":\"schemars\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"schemars_1\",\"package\":\"schemars\",\"req\":\"^1.0.2\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.152\"},{\"kind\":\"dev\",\"name\":\"serde-xml-rs\",\"req\":\"^0.8.1\"},{\"default_features\":false,\"features\":[\"result\"],\"name\":\"serde_core\",\"req\":\"^1.0.225\"},{\"default_features\":false,\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.145\"},{\"features\":[\"preserve_order\"],\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.124\"},{\"name\":\"serde_with_macros\",\"optional\":true,\"req\":\"=3.16.1\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.2\"},{\"default_features\":false,\"name\":\"smallvec_1\",\"optional\":true,\"package\":\"smallvec\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"time_0_3\",\"optional\":true,\"package\":\"time\",\"req\":\"~0.3.36\"}],\"features\":{\"alloc\":[\"serde_core/alloc\",\"base64?/alloc\",\"chrono_0_4?/alloc\",\"hex?/alloc\",\"serde_json?/alloc\",\"time_0_3?/alloc\"],\"base64\":[\"dep:base64\",\"alloc\"],\"chrono\":[\"chrono_0_4\"],\"chrono_0_4\":[\"dep:chrono_0_4\"],\"default\":[\"std\",\"macros\"],\"guide\":[\"dep:document-features\",\"macros\",\"std\"],\"hashbrown_0_14\":[\"dep:hashbrown_0_14\",\"alloc\"],\"hashbrown_0_15\":[\"dep:hashbrown_0_15\",\"alloc\"],\"hashbrown_0_16\":[\"dep:hashbrown_0_16\",\"alloc\"],\"hex\":[\"dep:hex\",\"alloc\"],\"indexmap\":[\"indexmap_1\"],\"indexmap_1\":[\"dep:indexmap_1\",\"alloc\"],\"indexmap_2\":[\"dep:indexmap_2\",\"alloc\"],\"json\":[\"dep:serde_json\",\"alloc\"],\"macros\":[\"dep:serde_with_macros\"],\"schemars_0_8\":[\"dep:schemars_0_8\",\"std\",\"serde_with_macros?/schemars_0_8\"],\"schemars_0_9\":[\"dep:schemars_0_9\",\"alloc\",\"serde_with_macros?/schemars_0_9\",\"dep:serde_json\"],\"schemars_1\":[\"dep:schemars_1\",\"alloc\",\"serde_with_macros?/schemars_1\",\"dep:serde_json\"],\"smallvec_1\":[\"dep:smallvec_1\"],\"std\":[\"alloc\",\"serde_core/std\",\"chrono_0_4?/clock\",\"chrono_0_4?/std\",\"indexmap_1?/std\",\"indexmap_2?/std\",\"time_0_3?/serde-well-known\",\"time_0_3?/std\",\"schemars_0_9?/std\",\"schemars_1?/std\"],\"time_0_3\":[\"dep:time_0_3\"]}}", "serde_with_macros_3.16.1": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.21.0\"},{\"kind\":\"dev\",\"name\":\"expect-test\",\"req\":\"^1.5.1\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.3\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.1\"},{\"name\":\"quote\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.12.1\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.22\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.152\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.25\"},{\"features\":[\"extra-traits\",\"full\",\"parsing\"],\"name\":\"syn\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.111\"}],\"features\":{\"schemars_0_8\":[],\"schemars_0_9\":[],\"schemars_1\":[]}}", "serde_yaml_0.9.34+deprecated": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.79\"},{\"name\":\"indexmap\",\"req\":\"^2.2.1\"},{\"kind\":\"dev\",\"name\":\"indoc\",\"req\":\"^2.0\"},{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"name\":\"ryu\",\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0.195\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.195\"},{\"name\":\"unsafe-libyaml\",\"req\":\"^0.2.11\"}],\"features\":{}}", - "serial2_0.2.31": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert2\",\"req\":\"^0.3.11\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\",\"target\":\"cfg(unix)\"},{\"name\":\"libc\",\"req\":\"^0.2.109\",\"target\":\"cfg(unix)\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.108\"},{\"features\":[\"commapi\",\"fileapi\",\"handleapi\",\"ioapiset\",\"std\",\"synchapi\",\"winbase\",\"winerror\",\"winreg\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{\"doc\":[],\"doc-cfg\":[],\"rs4xx\":[],\"serde\":[\"dep:serde\"],\"unix\":[],\"windows\":[]}}", - "serial_test_3.2.0": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"env_logger\",\"optional\":true,\"req\":\">=0.6.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"fslock\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"executor\"],\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"use_std\"],\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\">=0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\">=0.4.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19\"},{\"default_features\":false,\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"scc\",\"req\":\"^2\"},{\"name\":\"serial_test_derive\",\"req\":\"~3.2.0\"}],\"features\":{\"async\":[\"dep:futures\",\"serial_test_derive/async\"],\"default\":[\"logging\",\"async\"],\"docsrs\":[\"dep:document-features\"],\"file_locks\":[\"dep:fslock\"],\"logging\":[\"dep:log\"],\"test_logging\":[\"logging\",\"dep:env_logger\",\"serial_test_derive/test_logging\"]}}", - "serial_test_derive_3.2.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\">=0.6.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"full\",\"printing\",\"parsing\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{\"async\":[],\"default\":[],\"test_logging\":[]}}", + "serial2_0.2.33": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert2\",\"req\":\"^0.3.11\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\",\"target\":\"cfg(unix)\"},{\"name\":\"libc\",\"req\":\"^0.2.109\",\"target\":\"cfg(unix)\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.108\"},{\"features\":[\"commapi\",\"fileapi\",\"handleapi\",\"ioapiset\",\"std\",\"synchapi\",\"winbase\",\"winerror\",\"winreg\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{\"doc\":[],\"doc-cfg\":[],\"rs4xx\":[],\"serde\":[\"dep:serde\"],\"unix\":[],\"windows\":[]}}", + "serial_test_3.3.1": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"env_logger\",\"optional\":true,\"req\":\">=0.6.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"fslock\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-executor\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"use_std\"],\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\">=0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\">=0.4.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19\"},{\"default_features\":false,\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"scc\",\"req\":\"^2\"},{\"name\":\"serial_test_derive\",\"req\":\"~3.3.1\"}],\"features\":{\"async\":[\"dep:futures-executor\",\"dep:futures-util\",\"serial_test_derive/async\"],\"default\":[\"logging\",\"async\"],\"docsrs\":[\"dep:document-features\"],\"file_locks\":[\"dep:fslock\"],\"logging\":[\"dep:log\"],\"test_logging\":[\"logging\",\"dep:env_logger\",\"serial_test_derive/test_logging\"]}}", + "serial_test_derive_3.3.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\">=0.6.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"full\",\"printing\",\"parsing\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{\"async\":[],\"default\":[],\"file_locks\":[],\"test_logging\":[]}}", "sha1_0.10.6": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"sha1-asm\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"}],\"features\":{\"asm\":[\"sha1-asm\"],\"compress\":[],\"default\":[\"std\"],\"force-soft\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "sha1_smol_1.0.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"openssl\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\"]}}", "sha2_0.10.9": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"sha2-asm\",\"optional\":true,\"req\":\"^0.6.1\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"}],\"features\":{\"asm\":[\"sha2-asm\"],\"asm-aarch64\":[\"asm\"],\"compress\":[],\"default\":[\"std\"],\"force-soft\":[],\"force-soft-compact\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "sharded-slab_0.1.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"indexmap\",\"req\":\"^1\"},{\"name\":\"lazy_static\",\"req\":\"^1\"},{\"features\":[\"checkpoint\"],\"name\":\"loom\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(loom)\"},{\"features\":[\"checkpoint\"],\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.5\",\"target\":\"cfg(loom)\"},{\"kind\":\"dev\",\"name\":\"memory-stats\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"slab\",\"req\":\"^0.4.2\"}],\"features\":{}}", "shared_library_0.1.9": "{\"dependencies\":[{\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2\"}],\"features\":{}}", - "shell-words_1.1.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "shell-words_1.1.1": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "shlex_1.3.0": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "signal-hook-mio_0.2.4": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"~0.2\"},{\"name\":\"mio-0_6\",\"optional\":true,\"package\":\"mio\",\"req\":\"~0.6\"},{\"features\":[\"os-util\",\"uds\"],\"name\":\"mio-0_7\",\"optional\":true,\"package\":\"mio\",\"req\":\"~0.7\"},{\"features\":[\"os-util\",\"os-poll\",\"uds\"],\"kind\":\"dev\",\"name\":\"mio-0_7\",\"package\":\"mio\",\"req\":\"~0.7\"},{\"features\":[\"net\",\"os-ext\"],\"name\":\"mio-0_8\",\"optional\":true,\"package\":\"mio\",\"req\":\"~0.8\"},{\"features\":[\"net\",\"os-ext\"],\"name\":\"mio-1_0\",\"optional\":true,\"package\":\"mio\",\"req\":\"~1.0\"},{\"name\":\"mio-uds\",\"optional\":true,\"req\":\"~0.6\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"~0.5\"},{\"name\":\"signal-hook\",\"req\":\"~0.3\"}],\"features\":{\"support-v0_6\":[\"mio-0_6\",\"mio-uds\"],\"support-v0_7\":[\"mio-0_7\"],\"support-v0_8\":[\"mio-0_8\"],\"support-v1_0\":[\"mio-1_0\"]}}", - "signal-hook-registry_1.4.5": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"signal-hook\",\"req\":\"~0.3\"}],\"features\":{}}", + "signal-hook-mio_0.2.5": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"~0.2\"},{\"name\":\"mio-0_6\",\"optional\":true,\"package\":\"mio\",\"req\":\"~0.6\"},{\"features\":[\"os-util\",\"uds\"],\"name\":\"mio-0_7\",\"optional\":true,\"package\":\"mio\",\"req\":\"~0.7\"},{\"features\":[\"os-util\",\"os-poll\",\"uds\"],\"kind\":\"dev\",\"name\":\"mio-0_7\",\"package\":\"mio\",\"req\":\"~0.7\"},{\"features\":[\"net\",\"os-ext\"],\"name\":\"mio-0_8\",\"optional\":true,\"package\":\"mio\",\"req\":\"~0.8\"},{\"features\":[\"net\",\"os-ext\"],\"name\":\"mio-1_0\",\"optional\":true,\"package\":\"mio\",\"req\":\"^1.0\"},{\"name\":\"mio-uds\",\"optional\":true,\"req\":\"~0.6\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"~3\"},{\"name\":\"signal-hook\",\"req\":\"~0.3\"}],\"features\":{\"support-v0_6\":[\"mio-0_6\",\"mio-uds\"],\"support-v0_7\":[\"mio-0_7\"],\"support-v0_8\":[\"mio-0_8\"],\"support-v1_0\":[\"mio-1_0\"]}}", + "signal-hook-registry_1.4.8": "{\"dependencies\":[{\"name\":\"errno\",\"req\":\">=0.2, <0.4\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"signal-hook\",\"req\":\"~0.3\"}],\"features\":{}}", "signal-hook_0.3.18": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^0.7\"},{\"name\":\"signal-hook-registry\",\"req\":\"^1.4\"}],\"features\":{\"channel\":[],\"default\":[\"channel\",\"iterator\"],\"extended-siginfo\":[\"channel\",\"iterator\",\"extended-siginfo-raw\"],\"extended-siginfo-raw\":[\"cc\"],\"iterator\":[\"channel\"]}}", "signature_2.2.0": "{\"dependencies\":[{\"name\":\"derive\",\"optional\":true,\"package\":\"signature_derive\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"digest\",\"optional\":true,\"req\":\"^0.10.6\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\",\"rand_core?/std\"]}}", - "simd-adler32_0.3.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adler\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"adler32\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"const-generics\":[],\"default\":[\"std\",\"const-generics\"],\"nightly\":[],\"std\":[]}}", + "simd-adler32_0.3.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adler\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"adler32\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"const-generics\":[],\"default\":[\"std\",\"const-generics\"],\"nightly\":[],\"std\":[]}}", "simdutf8_0.1.5": "{\"dependencies\":[],\"features\":{\"aarch64_neon\":[],\"aarch64_neon_prefetch\":[],\"default\":[\"std\"],\"hints\":[],\"public_imp\":[],\"std\":[]}}", "similar_2.7.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"optional\":true,\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"console\",\"req\":\"^0.15.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.10.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.130\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.68\"},{\"name\":\"unicode-segmentation\",\"optional\":true,\"req\":\"^1.7.1\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1\"}],\"features\":{\"bytes\":[\"bstr\",\"text\"],\"default\":[\"text\"],\"inline\":[\"text\"],\"text\":[],\"unicode\":[\"text\",\"unicode-segmentation\",\"bstr?/unicode\",\"bstr?/std\"],\"wasm32_web_time\":[\"web-time\"]}}", - "siphasher_1.0.1": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde_no_std\":[\"serde/alloc\"],\"serde_std\":[\"std\",\"serde/std\"],\"std\":[]}}", - "slab_0.4.11": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "siphasher_1.0.2": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde_no_std\":[\"serde/alloc\"],\"serde_std\":[\"std\",\"serde/std\"],\"std\":[]}}", + "slab_0.4.12": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "smallvec_1.15.1": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"bincode\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"bincode1\",\"package\":\"bincode\",\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"debugger_test\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"debugger_test_parser\",\"req\":\"^0.1.0\"},{\"default_features\":false,\"name\":\"malloc_size_of\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"unty\",\"optional\":true,\"req\":\"^0.0.4\"}],\"features\":{\"const_generics\":[],\"const_new\":[\"const_generics\"],\"debugger_visualizer\":[],\"drain_filter\":[],\"drain_keep_rest\":[\"drain_filter\"],\"impl_bincode\":[\"bincode\",\"unty\"],\"may_dangle\":[],\"specialization\":[],\"union\":[],\"write\":[]}}", "smawk_0.3.2": "{\"dependencies\":[{\"name\":\"ndarray\",\"optional\":true,\"req\":\"^0.15.4\"},{\"kind\":\"dev\",\"name\":\"num-traits\",\"req\":\"^0.2.14\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"rand_chacha\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9.4\"}],\"features\":{}}", "smol_str_0.3.5": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.4.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.5\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.2\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"serde_core?/std\",\"borsh?/std\"]}}", "socket2_0.5.10": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.171\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_System_IO\",\"Win32_System_Threading\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(windows)\"}],\"features\":{\"all\":[]}}", - "socket2_0.6.1": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.172\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_System_IO\",\"Win32_System_Threading\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.60\",\"target\":\"cfg(windows)\"}],\"features\":{\"all\":[]}}", + "socket2_0.6.2": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.172\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_System_IO\",\"Win32_System_Threading\",\"Win32_System_WindowsProgramming\"],\"name\":\"windows-sys\",\"req\":\"^0.60\",\"target\":\"cfg(windows)\"}],\"features\":{\"all\":[]}}", "spin_0.9.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\"},{\"name\":\"lock_api_crate\",\"optional\":true,\"package\":\"lock_api\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"barrier\":[\"mutex\"],\"default\":[\"lock_api\",\"mutex\",\"spin_mutex\",\"rwlock\",\"once\",\"lazy\",\"barrier\"],\"fair_mutex\":[\"mutex\"],\"lazy\":[\"once\"],\"lock_api\":[\"lock_api_crate\"],\"mutex\":[],\"once\":[],\"portable_atomic\":[\"portable-atomic\"],\"rwlock\":[],\"spin_mutex\":[\"mutex\"],\"std\":[],\"ticket_mutex\":[\"mutex\"],\"use_ticket_mutex\":[\"mutex\",\"ticket_mutex\"]}}", "spki_0.7.3": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"base64ct\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"oid\"],\"name\":\"der\",\"req\":\"^0.7.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"alloc\":[\"base64ct?/alloc\",\"der/alloc\"],\"arbitrary\":[\"std\",\"dep:arbitrary\",\"der/arbitrary\"],\"base64\":[\"dep:base64ct\"],\"fingerprint\":[\"sha2\"],\"pem\":[\"alloc\",\"der/pem\"],\"std\":[\"der/std\",\"alloc\"]}}", "sqlx-core_0.8.6": "{\"dependencies\":[{\"name\":\"async-io\",\"optional\":true,\"req\":\"^1.9.0\"},{\"name\":\"async-std\",\"optional\":true,\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"base64\",\"req\":\"^0.22.0\"},{\"name\":\"bigdecimal\",\"optional\":true,\"req\":\"^0.4.0\"},{\"name\":\"bit-vec\",\"optional\":true,\"req\":\"^0.6.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"bytes\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"clock\"],\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.34\"},{\"name\":\"crc\",\"optional\":true,\"req\":\"^3\"},{\"name\":\"crossbeam-queue\",\"req\":\"^0.3.2\"},{\"name\":\"either\",\"req\":\"^1.6.1\"},{\"name\":\"event-listener\",\"req\":\"^5.2.0\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.19\"},{\"name\":\"futures-intrusive\",\"req\":\"^0.5.0\"},{\"name\":\"futures-io\",\"req\":\"^0.3.24\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\",\"io\"],\"name\":\"futures-util\",\"req\":\"^0.3.19\"},{\"name\":\"hashbrown\",\"req\":\"^0.15.0\"},{\"name\":\"hashlink\",\"req\":\"^0.10.0\"},{\"name\":\"indexmap\",\"req\":\"^2.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.3.0\"},{\"name\":\"ipnetwork\",\"optional\":true,\"req\":\"^0.20.0\"},{\"default_features\":false,\"name\":\"log\",\"req\":\"^0.4.18\"},{\"name\":\"mac_address\",\"optional\":true,\"req\":\"^1.1.5\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2.4.1\"},{\"name\":\"native-tls\",\"optional\":true,\"req\":\"^0.2.10\"},{\"name\":\"once_cell\",\"req\":\"^1.9.0\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1.0\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.5.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rust_decimal\",\"optional\":true,\"req\":\"^1.26.1\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.15\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8.0\"},{\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.132\"},{\"features\":[\"raw_value\"],\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.73\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10.0\"},{\"name\":\"smallvec\",\"req\":\"^1.7.0\"},{\"default_features\":false,\"features\":[\"postgres\",\"sqlite\",\"mysql\",\"migrate\",\"macros\",\"time\",\"uuid\"],\"kind\":\"dev\",\"name\":\"sqlx\",\"req\":\"=0.8.6\"},{\"name\":\"thiserror\",\"req\":\"^2.0.0\"},{\"features\":[\"formatting\",\"parsing\",\"macros\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.36\"},{\"default_features\":false,\"features\":[\"time\",\"net\",\"sync\",\"fs\",\"io-util\",\"rt\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\"],\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1.8\"},{\"features\":[\"log\"],\"name\":\"tracing\",\"req\":\"^0.1.37\"},{\"name\":\"url\",\"req\":\"^2.2.2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.1.2\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^0.26\"}],\"features\":{\"_rt-async-std\":[\"async-std\",\"async-io\"],\"_rt-tokio\":[\"tokio\",\"tokio-stream\"],\"_tls-native-tls\":[\"native-tls\"],\"_tls-none\":[],\"_tls-rustls\":[\"rustls\"],\"_tls-rustls-aws-lc-rs\":[\"_tls-rustls\",\"rustls/aws-lc-rs\",\"webpki-roots\"],\"_tls-rustls-ring-native-roots\":[\"_tls-rustls\",\"rustls/ring\",\"rustls-native-certs\"],\"_tls-rustls-ring-webpki\":[\"_tls-rustls\",\"rustls/ring\",\"webpki-roots\"],\"any\":[],\"default\":[],\"json\":[\"serde\",\"serde_json\"],\"migrate\":[\"sha2\",\"crc\"],\"offline\":[\"serde\",\"either/serde\"]}}", @@ -1158,7 +1211,7 @@ "sqlx-sqlite_0.8.6": "{\"dependencies\":[{\"name\":\"atoi\",\"req\":\"^2.0\"},{\"default_features\":false,\"features\":[\"std\",\"clock\"],\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.34\"},{\"default_features\":false,\"features\":[\"async\"],\"name\":\"flume\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"features\":[\"sink\",\"alloc\",\"std\"],\"name\":\"futures-channel\",\"req\":\"^0.3.19\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.19\"},{\"name\":\"futures-executor\",\"req\":\"^0.3.19\"},{\"name\":\"futures-intrusive\",\"req\":\"^0.5.0\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"name\":\"futures-util\",\"req\":\"^0.3.19\"},{\"default_features\":false,\"features\":[\"pkg-config\",\"vcpkg\",\"unlock_notify\"],\"name\":\"libsqlite3-sys\",\"req\":\"^0.30.1\"},{\"name\":\"log\",\"req\":\"^0.4.18\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1.0\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.5.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.145\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"macros\",\"runtime-tokio\",\"tls-none\",\"sqlite\"],\"kind\":\"dev\",\"name\":\"sqlx\",\"req\":\"=0.8.6\"},{\"name\":\"sqlx-core\",\"req\":\"=0.8.6\"},{\"name\":\"thiserror\",\"req\":\"^2.0.0\"},{\"features\":[\"formatting\",\"parsing\",\"macros\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.36\"},{\"features\":[\"log\"],\"name\":\"tracing\",\"req\":\"^0.1.37\"},{\"name\":\"url\",\"req\":\"^2.2.2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.1.2\"}],\"features\":{\"any\":[\"sqlx-core/any\"],\"bundled\":[\"libsqlite3-sys/bundled\"],\"chrono\":[\"dep:chrono\",\"sqlx-core/chrono\"],\"json\":[\"sqlx-core/json\",\"serde\"],\"migrate\":[\"sqlx-core/migrate\"],\"offline\":[\"sqlx-core/offline\",\"serde\"],\"preupdate-hook\":[\"libsqlite3-sys/preupdate_hook\"],\"regexp\":[\"dep:regex\"],\"time\":[\"dep:time\",\"sqlx-core/time\"],\"unbundled\":[\"libsqlite3-sys/buildtime_bindgen\"],\"uuid\":[\"dep:uuid\",\"sqlx-core/uuid\"]}}", "sqlx_0.8.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.52\"},{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.12\"},{\"features\":[\"async_tokio\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"dotenvy\",\"req\":\"^0.15.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.19\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"libsqlite3-sys\",\"req\":\"^0.30.1\"},{\"features\":[\"bundled-sqlcipher\"],\"kind\":\"dev\",\"name\":\"libsqlite3-sys\",\"req\":\"^0.30.1\",\"target\":\"cfg(sqlite_test_sqlcipher)\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"rand_xoshiro\",\"req\":\"^0.6.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.132\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.73\"},{\"features\":[\"offline\",\"migrate\"],\"name\":\"sqlx-core\",\"req\":\"=0.8.6\"},{\"name\":\"sqlx-macros\",\"optional\":true,\"req\":\"=0.8.6\"},{\"name\":\"sqlx-mysql\",\"optional\":true,\"req\":\"=0.8.6\"},{\"name\":\"sqlx-postgres\",\"optional\":true,\"req\":\"=0.8.6\"},{\"name\":\"sqlx-sqlite\",\"optional\":true,\"req\":\"=0.8.6\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"},{\"kind\":\"dev\",\"name\":\"time_\",\"package\":\"time\",\"req\":\"^0.3.2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.15.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.53\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2.2.2\"}],\"features\":{\"_rt-async-std\":[],\"_rt-tokio\":[],\"_sqlite\":[],\"_unstable-all-types\":[\"bigdecimal\",\"rust_decimal\",\"json\",\"time\",\"chrono\",\"ipnet\",\"ipnetwork\",\"mac_address\",\"uuid\",\"bit-vec\",\"bstr\"],\"all-databases\":[\"mysql\",\"sqlite\",\"postgres\",\"any\"],\"any\":[\"sqlx-core/any\",\"sqlx-mysql?/any\",\"sqlx-postgres?/any\",\"sqlx-sqlite?/any\"],\"bigdecimal\":[\"sqlx-core/bigdecimal\",\"sqlx-macros?/bigdecimal\",\"sqlx-mysql?/bigdecimal\",\"sqlx-postgres?/bigdecimal\"],\"bit-vec\":[\"sqlx-core/bit-vec\",\"sqlx-macros?/bit-vec\",\"sqlx-postgres?/bit-vec\"],\"bstr\":[\"sqlx-core/bstr\"],\"chrono\":[\"sqlx-core/chrono\",\"sqlx-macros?/chrono\",\"sqlx-mysql?/chrono\",\"sqlx-postgres?/chrono\",\"sqlx-sqlite?/chrono\"],\"default\":[\"any\",\"macros\",\"migrate\",\"json\"],\"derive\":[\"sqlx-macros/derive\"],\"ipnet\":[\"sqlx-core/ipnet\",\"sqlx-macros?/ipnet\",\"sqlx-postgres?/ipnet\"],\"ipnetwork\":[\"sqlx-core/ipnetwork\",\"sqlx-macros?/ipnetwork\",\"sqlx-postgres?/ipnetwork\"],\"json\":[\"sqlx-core/json\",\"sqlx-macros?/json\",\"sqlx-mysql?/json\",\"sqlx-postgres?/json\",\"sqlx-sqlite?/json\"],\"mac_address\":[\"sqlx-core/mac_address\",\"sqlx-macros?/mac_address\",\"sqlx-postgres?/mac_address\"],\"macros\":[\"derive\",\"sqlx-macros/macros\"],\"migrate\":[\"sqlx-core/migrate\",\"sqlx-macros?/migrate\",\"sqlx-mysql?/migrate\",\"sqlx-postgres?/migrate\",\"sqlx-sqlite?/migrate\"],\"mysql\":[\"sqlx-mysql\",\"sqlx-macros?/mysql\"],\"postgres\":[\"sqlx-postgres\",\"sqlx-macros?/postgres\"],\"regexp\":[\"sqlx-sqlite?/regexp\"],\"runtime-async-std\":[\"_rt-async-std\",\"sqlx-core/_rt-async-std\",\"sqlx-macros?/_rt-async-std\"],\"runtime-async-std-native-tls\":[\"runtime-async-std\",\"tls-native-tls\"],\"runtime-async-std-rustls\":[\"runtime-async-std\",\"tls-rustls-ring\"],\"runtime-tokio\":[\"_rt-tokio\",\"sqlx-core/_rt-tokio\",\"sqlx-macros?/_rt-tokio\"],\"runtime-tokio-native-tls\":[\"runtime-tokio\",\"tls-native-tls\"],\"runtime-tokio-rustls\":[\"runtime-tokio\",\"tls-rustls-ring\"],\"rust_decimal\":[\"sqlx-core/rust_decimal\",\"sqlx-macros?/rust_decimal\",\"sqlx-mysql?/rust_decimal\",\"sqlx-postgres?/rust_decimal\"],\"sqlite\":[\"_sqlite\",\"sqlx-sqlite/bundled\",\"sqlx-macros?/sqlite\"],\"sqlite-preupdate-hook\":[\"sqlx-sqlite/preupdate-hook\"],\"sqlite-unbundled\":[\"_sqlite\",\"sqlx-sqlite/unbundled\",\"sqlx-macros?/sqlite-unbundled\"],\"time\":[\"sqlx-core/time\",\"sqlx-macros?/time\",\"sqlx-mysql?/time\",\"sqlx-postgres?/time\",\"sqlx-sqlite?/time\"],\"tls-native-tls\":[\"sqlx-core/_tls-native-tls\",\"sqlx-macros?/_tls-native-tls\"],\"tls-none\":[],\"tls-rustls\":[\"tls-rustls-ring\"],\"tls-rustls-aws-lc-rs\":[\"sqlx-core/_tls-rustls-aws-lc-rs\",\"sqlx-macros?/_tls-rustls-aws-lc-rs\"],\"tls-rustls-ring\":[\"tls-rustls-ring-webpki\"],\"tls-rustls-ring-native-roots\":[\"sqlx-core/_tls-rustls-ring-native-roots\",\"sqlx-macros?/_tls-rustls-ring-native-roots\"],\"tls-rustls-ring-webpki\":[\"sqlx-core/_tls-rustls-ring-webpki\",\"sqlx-macros?/_tls-rustls-ring-webpki\"],\"uuid\":[\"sqlx-core/uuid\",\"sqlx-macros?/uuid\",\"sqlx-mysql?/uuid\",\"sqlx-postgres?/uuid\",\"sqlx-sqlite?/uuid\"]}}", "sse-stream_0.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"features\":[\"tracing\"],\"kind\":\"dev\",\"name\":\"axum\",\"req\":\"^0.8\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"client\",\"http1\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"stream\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"io\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"}],\"features\":{\"default\":[],\"tracing\":[\"dep:tracing\"]}}", - "stable_deref_trait_1.2.0": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", + "stable_deref_trait_1.2.1": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "starlark_0.13.0": "{\"dependencies\":[{\"features\":[\"bumpalo\",\"num-bigint\"],\"name\":\"allocative\",\"req\":\"^0.3.4\"},{\"name\":\"anyhow\",\"req\":\"^1.0.65\"},{\"name\":\"bumpalo\",\"req\":\"^3.8\"},{\"name\":\"cmp_any\",\"req\":\"^0.8.1\"},{\"name\":\"debugserver-types\",\"req\":\"^0.5.0\"},{\"name\":\"derivative\",\"req\":\"^2.2\"},{\"features\":[\"full\"],\"name\":\"derive_more\",\"req\":\"^1.0.0\"},{\"name\":\"display_container\",\"req\":\"^0.9.0\"},{\"name\":\"dupe\",\"req\":\"^0.9.0\"},{\"name\":\"either\",\"req\":\"^1.8\"},{\"name\":\"erased-serde\",\"req\":\"^0.3.12\"},{\"features\":[\"raw\"],\"name\":\"hashbrown\",\"req\":\"^0.14.3\"},{\"name\":\"inventory\",\"req\":\"^0.3.8\"},{\"name\":\"itertools\",\"req\":\"^0.13.0\"},{\"name\":\"maplit\",\"req\":\"^1.0.2\"},{\"name\":\"memoffset\",\"req\":\"^0.6.4\"},{\"name\":\"num-bigint\",\"req\":\"^0.4.3\"},{\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.8\"},{\"name\":\"paste\",\"req\":\"^1.0\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"name\":\"regex\",\"req\":\"^1.5.4\"},{\"name\":\"rustyline\",\"req\":\"^14.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"starlark_derive\",\"req\":\"^0.13.0\"},{\"name\":\"starlark_map\",\"req\":\"^0.13.0\"},{\"name\":\"starlark_syntax\",\"req\":\"^0.13.0\"},{\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"name\":\"strsim\",\"req\":\"^0.10.0\"},{\"name\":\"textwrap\",\"req\":\"^0.11\"},{\"name\":\"thiserror\",\"req\":\"^1.0.36\"}],\"features\":{}}", "starlark_derive_0.13.0": "{\"dependencies\":[{\"name\":\"dupe\",\"req\":\"^0.9.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"extra-traits\",\"full\",\"visit\",\"visit-mut\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "starlark_map_0.13.0": "{\"dependencies\":[{\"features\":[\"hashbrown\"],\"name\":\"allocative\",\"req\":\"^0.3.4\"},{\"name\":\"dupe\",\"req\":\"^0.9.0\"},{\"name\":\"equivalent\",\"req\":\"^1.0.0\"},{\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"features\":[\"raw\"],\"name\":\"hashbrown\",\"req\":\"^0.14.3\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.48\"}],\"features\":{}}", @@ -1177,17 +1230,17 @@ "supports-color_2.1.0": "{\"dependencies\":[{\"name\":\"is-terminal\",\"req\":\"^0.4.0\"},{\"name\":\"is_ci\",\"req\":\"^1.1.1\"}],\"features\":{}}", "supports-color_3.0.2": "{\"dependencies\":[{\"name\":\"is_ci\",\"req\":\"^1.2.0\"}],\"features\":{}}", "syn_1.0.109": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.46\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1.0\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.1\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", - "syn_2.0.104": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1\"},{\"name\":\"unicode-ident\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\",\"target\":\"cfg(not(miri))\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"dep:quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote?/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", + "syn_2.0.114": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.13\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1\"},{\"name\":\"unicode-ident\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\",\"target\":\"cfg(not(miri))\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"dep:quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote?/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", "sync_wrapper_1.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"}],\"features\":{\"futures\":[\"futures-core\"]}}", "synstructure_0.13.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"visit\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"synstructure_test_traits\",\"req\":\"^0.1\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"syn/proc-macro\",\"quote/proc-macro\"]}}", "sys-locale_0.3.2": "{\"dependencies\":[{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"features\":[\"Window\",\"WorkerGlobalScope\",\"Navigator\",\"WorkerNavigator\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"}],\"features\":{\"js\":[\"js-sys\",\"wasm-bindgen\",\"web-sys\"]}}", "system-configuration-sys_0.6.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.149\"}],\"features\":{}}", "system-configuration_0.6.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.6\"}],\"features\":{}}", "tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}", - "tempfile_3.23.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.0.0\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", + "tempfile_3.24.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.3\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", "term_0.7.0": "{\"dependencies\":[{\"name\":\"dirs-next\",\"req\":\"^2\"},{\"name\":\"rustversion\",\"req\":\"^1\",\"target\":\"cfg(windows)\"},{\"features\":[\"consoleapi\",\"wincon\",\"handleapi\",\"fileapi\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[]}}", "termcolor_1.4.1": "{\"dependencies\":[{\"name\":\"winapi-util\",\"req\":\"^0.1.3\",\"target\":\"cfg(windows)\"}],\"features\":{}}", - "terminal_size_0.4.2": "{\"dependencies\":[{\"features\":[\"termios\"],\"name\":\"rustix\",\"req\":\"^1.0.1\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\"],\"name\":\"windows-sys\",\"req\":\"^0.59.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "terminal_size_0.4.3": "{\"dependencies\":[{\"features\":[\"termios\"],\"name\":\"rustix\",\"req\":\"^1.0.1\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\"],\"name\":\"windows-sys\",\"req\":\"^0.60.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "termtree_0.5.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.10\"}],\"features\":{}}", "test-case-core_3.3.1": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{\"with-regex\":[]}}", "test-case-macros_3.3.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\",\"extra-traits\",\"parsing\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"default_features\":false,\"name\":\"test-case-core\",\"req\":\"^3.2.1\"}],\"features\":{\"with-regex\":[\"test-case-core/with-regex\"]}}", @@ -1197,65 +1250,68 @@ "textwrap_0.11.0": "{\"dependencies\":[{\"features\":[\"embed_all\"],\"name\":\"hyphenation\",\"optional\":true,\"req\":\"^0.7.1\"},{\"kind\":\"dev\",\"name\":\"lipsum\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand_xorshift\",\"req\":\"^0.1\"},{\"name\":\"term_size\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"unicode-width\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.6\"}],\"features\":{}}", "textwrap_0.16.2": "{\"dependencies\":[{\"features\":[\"embed_en-us\"],\"name\":\"hyphenation\",\"optional\":true,\"req\":\"^0.8.4\"},{\"name\":\"smawk\",\"optional\":true,\"req\":\"^0.3.2\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"termion\",\"req\":\"^4.0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"unic-emoji-char\",\"req\":\"^0.9.0\"},{\"name\":\"unicode-linebreak\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"unicode-width\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9.5\"}],\"features\":{\"default\":[\"unicode-linebreak\",\"unicode-width\",\"smawk\"]}}", "thiserror-impl_1.0.69": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"syn\",\"req\":\"^2.0.87\"}],\"features\":{}}", - "thiserror-impl_2.0.17": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"syn\",\"req\":\"^2.0.87\"}],\"features\":{}}", + "thiserror-impl_2.0.18": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"syn\",\"req\":\"^2.0.87\"}],\"features\":{}}", "thiserror_1.0.69": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.73\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"name\":\"thiserror-impl\",\"req\":\"=1.0.69\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.81\"}],\"features\":{}}", - "thiserror_2.0.17": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.73\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"name\":\"thiserror-impl\",\"req\":\"=2.0.17\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "thiserror_2.0.18": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.73\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0.18\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.13\"},{\"name\":\"thiserror-impl\",\"req\":\"=2.0.18\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.108\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "thread_local_1.1.9": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"}],\"features\":{\"nightly\":[]}}", "tiff_0.10.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crc32fast\",\"req\":\"^1.5\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.1\"},{\"name\":\"fax34\",\"optional\":true,\"package\":\"fax\",\"req\":\"^0.2.6\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0.20\"},{\"name\":\"half\",\"req\":\"^2.4.1\"},{\"name\":\"quick-error\",\"req\":\"^2.0.1\"},{\"name\":\"weezl\",\"optional\":true,\"req\":\"^0.1.10\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"},{\"name\":\"zune-jpeg\",\"optional\":true,\"req\":\"^0.4.17\"}],\"features\":{\"default\":[\"deflate\",\"fax\",\"jpeg\",\"lzw\"],\"deflate\":[\"dep:flate2\"],\"fax\":[\"dep:fax34\"],\"jpeg\":[\"dep:zune-jpeg\"],\"lzw\":[\"dep:weezl\"],\"zstd\":[\"dep:zstd\"]}}", - "time-core_0.1.6": "{\"dependencies\":[],\"features\":{}}", - "time-macros_0.2.24": "{\"dependencies\":[{\"name\":\"num-conv\",\"req\":\"^0.1.0\"},{\"name\":\"time-core\",\"req\":\"=0.1.6\"}],\"features\":{\"formatting\":[],\"large-dates\":[],\"parsing\":[],\"serde\":[]}}", - "time_0.3.44": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\",\"target\":\"cfg(bench)\"},{\"features\":[\"powerfmt\"],\"name\":\"deranged\",\"req\":\"^0.5.2\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1.0.1\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.58\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.98\",\"target\":\"cfg(target_family = \\\"unix\\\")\"},{\"name\":\"num-conv\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"num-conv\",\"req\":\"^0.1.0\"},{\"name\":\"num_threads\",\"optional\":true,\"req\":\"^0.1.2\",\"target\":\"cfg(target_family = \\\"unix\\\")\"},{\"default_features\":false,\"name\":\"powerfmt\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"rand08\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand08\",\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"name\":\"rand09\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.9.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand09\",\"package\":\"rand\",\"req\":\"^0.9.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.23.0\"},{\"kind\":\"dev\",\"name\":\"rstest_reuse\",\"req\":\"^0.7.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.184\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.184\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.68\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.126\"},{\"name\":\"time-core\",\"req\":\"=0.1.6\"},{\"name\":\"time-macros\",\"optional\":true,\"req\":\"=0.2.24\"},{\"kind\":\"dev\",\"name\":\"time-macros\",\"req\":\"=0.2.24\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.102\",\"target\":\"cfg(__ui_tests)\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"default\":[\"std\"],\"formatting\":[\"dep:itoa\",\"std\",\"time-macros?/formatting\"],\"large-dates\":[\"time-macros?/large-dates\"],\"local-offset\":[\"std\",\"dep:libc\",\"dep:num_threads\"],\"macros\":[\"dep:time-macros\"],\"parsing\":[\"time-macros?/parsing\"],\"quickcheck\":[\"dep:quickcheck\",\"alloc\",\"deranged/quickcheck\"],\"rand\":[\"rand08\",\"rand09\"],\"rand08\":[\"dep:rand08\",\"deranged/rand08\"],\"rand09\":[\"dep:rand09\",\"deranged/rand09\"],\"serde\":[\"dep:serde\",\"time-macros?/serde\",\"deranged/serde\"],\"serde-human-readable\":[\"serde\",\"formatting\",\"parsing\"],\"serde-well-known\":[\"serde\",\"formatting\",\"parsing\"],\"std\":[\"alloc\"],\"wasm-bindgen\":[\"dep:js-sys\"]}}", + "time-core_0.1.8": "{\"dependencies\":[],\"features\":{\"large-dates\":[]}}", + "time-macros_0.2.26": "{\"dependencies\":[{\"name\":\"num-conv\",\"req\":\"^0.2.0\"},{\"name\":\"time-core\",\"req\":\"=0.1.8\"}],\"features\":{\"formatting\":[],\"large-dates\":[],\"parsing\":[],\"serde\":[]}}", + "time_0.3.46": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.1\",\"target\":\"cfg(bench)\"},{\"features\":[\"powerfmt\"],\"name\":\"deranged\",\"req\":\"^0.5.2\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1.0.1\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.58\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.98\",\"target\":\"cfg(target_family = \\\"unix\\\")\"},{\"name\":\"num-conv\",\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"num-conv\",\"req\":\"^0.2.0\"},{\"name\":\"num_threads\",\"optional\":true,\"req\":\"^0.1.2\",\"target\":\"cfg(target_family = \\\"unix\\\")\"},{\"default_features\":false,\"name\":\"powerfmt\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"rand08\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rand08\",\"package\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"name\":\"rand09\",\"optional\":true,\"package\":\"rand\",\"req\":\"^0.9.2\"},{\"default_features\":false,\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand09\",\"package\":\"rand\",\"req\":\"^0.9.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.26.1\"},{\"kind\":\"dev\",\"name\":\"rstest_reuse\",\"req\":\"^0.7.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.184\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.68\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.126\"},{\"name\":\"time-core\",\"req\":\"=0.1.8\"},{\"name\":\"time-macros\",\"optional\":true,\"req\":\"=0.2.26\"},{\"kind\":\"dev\",\"name\":\"time-macros\",\"req\":\"=0.2.26\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.102\",\"target\":\"cfg(__ui_tests)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\"],\"default\":[\"std\"],\"formatting\":[\"dep:itoa\",\"std\",\"time-macros?/formatting\"],\"large-dates\":[\"time-core/large-dates\",\"time-macros?/large-dates\"],\"local-offset\":[\"std\",\"dep:libc\",\"dep:num_threads\"],\"macros\":[\"dep:time-macros\"],\"parsing\":[\"time-macros?/parsing\"],\"quickcheck\":[\"dep:quickcheck\",\"alloc\",\"deranged/quickcheck\"],\"rand\":[\"rand08\",\"rand09\"],\"rand08\":[\"dep:rand08\",\"deranged/rand08\"],\"rand09\":[\"dep:rand09\",\"deranged/rand09\"],\"serde\":[\"dep:serde_core\",\"time-macros?/serde\",\"deranged/serde\"],\"serde-human-readable\":[\"serde\",\"formatting\",\"parsing\"],\"serde-well-known\":[\"serde\",\"formatting\",\"parsing\"],\"std\":[\"alloc\"],\"wasm-bindgen\":[\"dep:js-sys\"]}}", "tiny-keccak_2.0.2": "{\"dependencies\":[{\"name\":\"crunchy\",\"req\":\"^0.2.2\"}],\"features\":{\"cshake\":[],\"default\":[],\"fips202\":[\"keccak\",\"shake\",\"sha3\"],\"k12\":[],\"keccak\":[],\"kmac\":[\"cshake\"],\"parallel_hash\":[\"cshake\"],\"sha3\":[],\"shake\":[],\"sp800\":[\"cshake\",\"kmac\",\"tuple_hash\"],\"tuple_hash\":[\"cshake\"]}}", "tiny_http_0.12.0": "{\"dependencies\":[{\"name\":\"ascii\",\"req\":\"^1.0\"},{\"name\":\"chunked_transfer\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"fdlimit\",\"req\":\"^0.1\"},{\"name\":\"httpdate\",\"req\":\"^1.0.2\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"name\":\"openssl\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"rustc-serialize\",\"req\":\"^0.3\"},{\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.20\"},{\"name\":\"rustls-pemfile\",\"optional\":true,\"req\":\"^0.2.1\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.6.0\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"default\":[],\"ssl\":[\"ssl-openssl\"],\"ssl-openssl\":[\"openssl\",\"zeroize\"],\"ssl-rustls\":[\"rustls\",\"rustls-pemfile\",\"zeroize\"]}}", - "tinystr_0.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"use-std\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.110\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"zerovec?/alloc\"],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde\"],\"std\":[],\"zerovec\":[\"dep:zerovec\"]}}", + "tinystr_0.8.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"use-std\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"zerovec?/alloc\"],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde_core\"],\"std\":[],\"zerovec\":[\"dep:zerovec\"]}}", "tinyvec_1.10.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"debugger_test\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"debugger_test_parser\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"generic-array\",\"optional\":true,\"req\":\"^1.1.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"smallvec\",\"req\":\"^1\"},{\"name\":\"tinyvec_macros\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{\"alloc\":[\"tinyvec_macros\"],\"debugger_visualizer\":[],\"default\":[],\"experimental_write_impl\":[],\"grab_spare_slice\":[],\"latest_stable_rust\":[\"rustc_1_61\"],\"nightly_slice_partition_dedup\":[],\"real_blackbox\":[\"criterion/real_blackbox\"],\"rustc_1_40\":[],\"rustc_1_55\":[],\"rustc_1_57\":[],\"rustc_1_61\":[\"rustc_1_57\"],\"std\":[\"alloc\"]}}", "tinyvec_macros_0.1.1": "{\"dependencies\":[],\"features\":{}}", "tokio-graceful_0.2.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\",\"target\":\"cfg(not(loom))\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\",\"target\":\"cfg(not(loom))\"},{\"features\":[\"server\",\"http1\",\"http2\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.0.1\",\"target\":\"cfg(not(loom))\"},{\"features\":[\"server\",\"server-auto\",\"http1\",\"http2\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.1\",\"target\":\"cfg(not(loom))\"},{\"features\":[\"futures\",\"checkpoint\"],\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"slab\",\"req\":\"^0.4\"},{\"features\":[\"rt\",\"signal\",\"sync\",\"macros\",\"time\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"net\",\"rt-multi-thread\",\"io-util\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"}],\"features\":{}}", "tokio-macros_2.6.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"}],\"features\":{}}", "tokio-native-tls_0.3.1": "{\"dependencies\":[{\"name\":\"native-tls\",\"req\":\"^0.2\"},{\"name\":\"tokio\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^0.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.6\"},{\"features\":[\"async-await\"],\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1\"},{\"features\":[\"macros\",\"rt\",\"rt-multi-thread\",\"io-util\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.6.0\"},{\"kind\":\"dev\",\"name\":\"openssl\",\"req\":\"^0.10\",\"target\":\"cfg(all(not(target_os = \\\"macos\\\"), not(windows), not(target_os = \\\"ios\\\")))\"},{\"kind\":\"dev\",\"name\":\"security-framework\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"ios\\\"))\"},{\"kind\":\"dev\",\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"features\":[\"lmcons\",\"basetsd\",\"minwinbase\",\"minwindef\",\"ntdef\",\"sysinfoapi\",\"timezoneapi\",\"wincrypt\",\"winerror\"],\"kind\":\"dev\",\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{\"vendored\":[\"native-tls/vendored\"]}}", - "tokio-rustls_0.26.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"argh\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.1\"},{\"features\":[\"pem\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.13\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"req\":\"^0.23.22\"},{\"name\":\"tokio\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^0.26\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"rustls/aws_lc_rs\"],\"default\":[\"logging\",\"tls12\",\"aws_lc_rs\"],\"early-data\":[],\"fips\":[\"rustls/fips\"],\"logging\":[\"rustls/logging\"],\"ring\":[\"rustls/ring\"],\"tls12\":[\"rustls/tls12\"]}}", + "tokio-rustls_0.26.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"argh\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.1\"},{\"features\":[\"pem\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"req\":\"^0.23.27\"},{\"name\":\"tokio\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"rustls/aws_lc_rs\"],\"brotli\":[\"rustls/brotli\"],\"default\":[\"logging\",\"tls12\",\"aws_lc_rs\"],\"early-data\":[],\"fips\":[\"rustls/fips\"],\"logging\":[\"rustls/logging\"],\"ring\":[\"rustls/ring\"],\"tls12\":[\"rustls/tls12\"],\"zlib\":[\"rustls/zlib\"]}}", "tokio-stream_0.1.18": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-stream\",\"req\":\"^0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"parking_lot\",\"req\":\"^0.12.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1.15.0\"},{\"features\":[\"full\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.0\"}],\"features\":{\"default\":[\"time\"],\"fs\":[\"tokio/fs\"],\"full\":[\"time\",\"net\",\"io-util\",\"fs\",\"sync\",\"signal\"],\"io-util\":[\"tokio/io-util\"],\"net\":[\"tokio/net\"],\"signal\":[\"tokio/signal\"],\"sync\":[\"tokio/sync\",\"tokio-util\"],\"time\":[\"tokio/time\"]}}", "tokio-test_0.4.5": "{\"dependencies\":[{\"name\":\"futures-core\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.0\"},{\"features\":[\"rt\",\"sync\",\"time\",\"test-util\"],\"name\":\"tokio\",\"req\":\"^1.2.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.2.0\"},{\"name\":\"tokio-stream\",\"req\":\"^0.1.1\"}],\"features\":{}}", "tokio-util_0.7.18": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-stream\",\"req\":\"^0.3.0\"},{\"name\":\"bytes\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.0\"},{\"name\":\"futures-core\",\"req\":\"^0.3.0\"},{\"name\":\"futures-io\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"futures-sink\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"futures-test\",\"req\":\"^0.3.5\"},{\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.0\"},{\"default_features\":false,\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15.0\"},{\"features\":[\"futures\",\"checkpoint\"],\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"kind\":\"dev\",\"name\":\"parking_lot\",\"req\":\"^0.12.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"name\":\"slab\",\"optional\":true,\"req\":\"^0.4.4\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1.44.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.29\"}],\"features\":{\"__docs_rs\":[\"futures-util\"],\"codec\":[],\"compat\":[\"futures-io\"],\"default\":[],\"full\":[\"codec\",\"compat\",\"io-util\",\"time\",\"net\",\"rt\",\"join-map\"],\"io\":[],\"io-util\":[\"io\",\"tokio/rt\",\"tokio/io-util\"],\"join-map\":[\"rt\",\"hashbrown\"],\"net\":[\"tokio/net\"],\"rt\":[\"tokio/rt\",\"tokio/sync\",\"futures-util\"],\"time\":[\"tokio/time\",\"slab\"]}}", "tokio_1.49.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-stream\",\"req\":\"^0.3\"},{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.58\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1.2.1\"},{\"features\":[\"async-await\"],\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"futures-concurrency\",\"req\":\"^7.6.3\"},{\"kind\":\"dev\",\"name\":\"futures-test\",\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"io-uring\",\"optional\":true,\"req\":\"^0.7.6\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.168\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.168\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.168\",\"target\":\"cfg(unix)\"},{\"features\":[\"futures\",\"checkpoint\"],\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"name\":\"mio\",\"optional\":true,\"req\":\"^1.0.1\"},{\"default_features\":false,\"features\":[\"os-poll\",\"os-ext\"],\"name\":\"mio\",\"optional\":true,\"req\":\"^1.0.1\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"features\":[\"tokio\"],\"kind\":\"dev\",\"name\":\"mio-aio\",\"req\":\"^1\",\"target\":\"cfg(target_os = \\\"freebsd\\\")\"},{\"kind\":\"dev\",\"name\":\"mockall\",\"req\":\"^0.13.0\"},{\"default_features\":false,\"features\":[\"aio\",\"fs\",\"socket\"],\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.29.0\",\"target\":\"cfg(unix)\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"},{\"name\":\"signal-hook-registry\",\"optional\":true,\"req\":\"^1.1.1\",\"target\":\"cfg(unix)\"},{\"name\":\"slab\",\"optional\":true,\"req\":\"^0.4.9\",\"target\":\"cfg(all(tokio_unstable, target_os = \\\"linux\\\"))\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\"^0.6.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"socket2\",\"req\":\"^0.6.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.1.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"name\":\"tokio-macros\",\"optional\":true,\"req\":\"~2.6.0\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4.0\"},{\"features\":[\"rt\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.29\",\"target\":\"cfg(tokio_unstable)\"},{\"kind\":\"dev\",\"name\":\"tracing-mock\",\"req\":\"=0.1.0-beta.1\",\"target\":\"cfg(all(tokio_unstable, target_has_atomic = \\\"64\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.0\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(target_os = \\\"wasi\\\")))\"},{\"name\":\"windows-sys\",\"optional\":true,\"req\":\"^0.61\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_Foundation\",\"Win32_Security_Authorization\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"fs\":[],\"full\":[\"fs\",\"io-util\",\"io-std\",\"macros\",\"net\",\"parking_lot\",\"process\",\"rt\",\"rt-multi-thread\",\"signal\",\"sync\",\"time\"],\"io-std\":[],\"io-uring\":[\"dep:io-uring\",\"libc\",\"mio/os-poll\",\"mio/os-ext\",\"dep:slab\"],\"io-util\":[\"bytes\"],\"macros\":[\"tokio-macros\"],\"net\":[\"libc\",\"mio/os-poll\",\"mio/os-ext\",\"mio/net\",\"socket2\",\"windows-sys/Win32_Foundation\",\"windows-sys/Win32_Security\",\"windows-sys/Win32_Storage_FileSystem\",\"windows-sys/Win32_System_Pipes\",\"windows-sys/Win32_System_SystemServices\"],\"process\":[\"bytes\",\"libc\",\"mio/os-poll\",\"mio/os-ext\",\"mio/net\",\"signal-hook-registry\",\"windows-sys/Win32_Foundation\",\"windows-sys/Win32_System_Threading\",\"windows-sys/Win32_System_WindowsProgramming\"],\"rt\":[],\"rt-multi-thread\":[\"rt\"],\"signal\":[\"libc\",\"mio/os-poll\",\"mio/net\",\"mio/os-ext\",\"signal-hook-registry\",\"windows-sys/Win32_Foundation\",\"windows-sys/Win32_System_Console\"],\"sync\":[],\"taskdump\":[\"dep:backtrace\"],\"test-util\":[\"rt\",\"sync\",\"time\"],\"time\":[]}}", "toml_0.5.11": "{\"dependencies\":[{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0.97\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[],\"preserve_order\":[\"indexmap\"]}}", - "toml_0.9.5": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.1.5\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.3.0\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.145\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.199\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.7\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.116\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.0\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.0\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^0.7.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.2\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.10\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", + "toml_0.9.11+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde_core?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", "toml_datetime_0.7.5+spec-1.1.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\"],\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"alloc\",\"serde_core?/std\"]}}", "toml_edit_0.23.10+spec-1.0.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"features\":[\"serde\"],\"name\":\"serde_spanned\",\"optional\":true,\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"name\":\"toml_datetime\",\"req\":\"^0.7.4\"},{\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.5\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\",\"display\"],\"default\":[\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"dep:serde_spanned\"],\"unbounded\":[]}}", "toml_edit_0.24.0+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"features\":[\"serde\"],\"name\":\"serde_spanned\",\"optional\":true,\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.6\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\",\"display\"],\"default\":[\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"dep:serde_spanned\"],\"unbounded\":[]}}", "toml_parser_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"anstream\",\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"default_features\":false,\"name\":\"winnow\",\"req\":\"^0.7.13\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\"],\"simd\":[\"winnow/simd\"],\"std\":[\"alloc\"],\"unsafe\":[]}}", "toml_writer_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml_old\",\"package\":\"toml\",\"req\":\"^0.5.11\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", - "tonic-prost_0.14.2": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"prost\",\"req\":\"^0.14\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"tonic\",\"req\":\"^0.14.0\"}],\"features\":{}}", - "tonic_0.14.2": "{\"dependencies\":[{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1.13\"},{\"default_features\":false,\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bytes\",\"req\":\"^1.0\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"http1\",\"http2\"],\"name\":\"hyper\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"hyper-timeout\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"tokio\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1\"},{\"name\":\"pin-project\",\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.0\"},{\"name\":\"sync_wrapper\",\"req\":\"^1.0.2\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"logging\",\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26.1\"},{\"default_features\":false,\"name\":\"tokio-stream\",\"req\":\"^0.1.16\"},{\"default_features\":false,\"name\":\"tower\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"load-shed\",\"timeout\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5\"},{\"name\":\"tower-layer\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13.0\"}],\"features\":{\"_tls-any\":[\"dep:tokio\",\"tokio?/rt\",\"tokio?/macros\",\"tls-connect-info\"],\"channel\":[\"dep:hyper\",\"hyper?/client\",\"dep:hyper-util\",\"hyper-util?/client-legacy\",\"dep:tower\",\"tower?/balance\",\"tower?/buffer\",\"tower?/discover\",\"tower?/limit\",\"tower?/load-shed\",\"tower?/util\",\"dep:tokio\",\"tokio?/time\",\"dep:hyper-timeout\"],\"codegen\":[\"dep:async-trait\"],\"default\":[\"router\",\"transport\",\"codegen\"],\"deflate\":[\"dep:flate2\"],\"gzip\":[\"dep:flate2\"],\"router\":[\"dep:axum\",\"dep:tower\",\"tower?/util\"],\"server\":[\"dep:h2\",\"dep:hyper\",\"hyper?/server\",\"dep:hyper-util\",\"hyper-util?/service\",\"hyper-util?/server-auto\",\"dep:socket2\",\"dep:tokio\",\"tokio?/macros\",\"tokio?/net\",\"tokio?/time\",\"tokio-stream/net\",\"dep:tower\",\"tower?/util\",\"tower?/limit\",\"tower?/load-shed\"],\"tls-aws-lc\":[\"_tls-any\",\"tokio-rustls/aws-lc-rs\"],\"tls-connect-info\":[\"dep:tokio-rustls\"],\"tls-native-roots\":[\"_tls-any\",\"channel\",\"dep:rustls-native-certs\"],\"tls-ring\":[\"_tls-any\",\"tokio-rustls/ring\"],\"tls-webpki-roots\":[\"_tls-any\",\"channel\",\"dep:webpki-roots\"],\"transport\":[\"server\",\"channel\"],\"zstd\":[\"dep:zstd\"]}}", - "tower-http_0.6.6": "{\"dependencies\":[{\"features\":[\"tokio\"],\"name\":\"async-compression\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bitflags\",\"req\":\"^2.0.2\"},{\"kind\":\"dev\",\"name\":\"brotli\",\"req\":\"^7\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.14\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.14\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"http-range-header\",\"optional\":true,\"req\":\"^0.4.0\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"client-legacy\",\"http1\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"iri-string\",\"optional\":true,\"req\":\"^0.7.0\"},{\"default_features\":false,\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.17\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.1.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"sync_wrapper\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.6\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"tower\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"buffer\",\"util\",\"retry\",\"make\",\"timeout\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5\"},{\"name\":\"tower-layer\",\"req\":\"^0.3.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"v4\"],\"kind\":\"dev\",\"name\":\"uuid\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"zstd\",\"req\":\"^0.13\"}],\"features\":{\"add-extension\":[],\"auth\":[\"base64\",\"validate-request\"],\"catch-panic\":[\"tracing\",\"futures-util/std\",\"dep:http-body\",\"dep:http-body-util\"],\"compression-br\":[\"async-compression/brotli\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"compression-deflate\":[\"async-compression/zlib\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"compression-full\":[\"compression-br\",\"compression-deflate\",\"compression-gzip\",\"compression-zstd\"],\"compression-gzip\":[\"async-compression/gzip\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"compression-zstd\":[\"async-compression/zstd\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"cors\":[],\"decompression-br\":[\"async-compression/brotli\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"decompression-deflate\":[\"async-compression/zlib\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"decompression-full\":[\"decompression-br\",\"decompression-deflate\",\"decompression-gzip\",\"decompression-zstd\"],\"decompression-gzip\":[\"async-compression/gzip\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"decompression-zstd\":[\"async-compression/zstd\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"default\":[],\"follow-redirect\":[\"futures-util\",\"dep:http-body\",\"iri-string\",\"tower/util\"],\"fs\":[\"futures-core\",\"futures-util\",\"dep:http-body\",\"dep:http-body-util\",\"tokio/fs\",\"tokio-util/io\",\"tokio/io-util\",\"dep:http-range-header\",\"mime_guess\",\"mime\",\"percent-encoding\",\"httpdate\",\"set-status\",\"futures-util/alloc\",\"tracing\"],\"full\":[\"add-extension\",\"auth\",\"catch-panic\",\"compression-full\",\"cors\",\"decompression-full\",\"follow-redirect\",\"fs\",\"limit\",\"map-request-body\",\"map-response-body\",\"metrics\",\"normalize-path\",\"propagate-header\",\"redirect\",\"request-id\",\"sensitive-headers\",\"set-header\",\"set-status\",\"timeout\",\"trace\",\"util\",\"validate-request\"],\"limit\":[\"dep:http-body\",\"dep:http-body-util\"],\"map-request-body\":[],\"map-response-body\":[],\"metrics\":[\"dep:http-body\",\"tokio/time\"],\"normalize-path\":[],\"propagate-header\":[],\"redirect\":[],\"request-id\":[\"uuid\"],\"sensitive-headers\":[],\"set-header\":[],\"set-status\":[],\"timeout\":[\"dep:http-body\",\"tokio/time\"],\"trace\":[\"dep:http-body\",\"tracing\"],\"util\":[\"tower\"],\"validate-request\":[\"mime\"]}}", + "tonic-prost_0.14.3": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"prost\",\"req\":\"^0.14\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"tonic\",\"req\":\"^0.14.0\"}],\"features\":{}}", + "tonic_0.14.3": "{\"dependencies\":[{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1.13\"},{\"default_features\":false,\"name\":\"axum\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bytes\",\"req\":\"^1.0\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"http\",\"req\":\"^1.1.0\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"http1\",\"http2\"],\"name\":\"hyper\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"hyper-timeout\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"tokio\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"^0.1.11\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1\"},{\"name\":\"pin-project\",\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.0\"},{\"name\":\"sync_wrapper\",\"req\":\"^1.0.2\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"logging\",\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26.1\"},{\"default_features\":false,\"name\":\"tokio-stream\",\"req\":\"^0.1.16\"},{\"default_features\":false,\"name\":\"tower\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"load-shed\",\"timeout\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5\"},{\"name\":\"tower-layer\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13.0\"}],\"features\":{\"_tls-any\":[\"dep:tokio\",\"tokio?/rt\",\"tokio?/macros\",\"tls-connect-info\"],\"channel\":[\"dep:hyper\",\"hyper?/client\",\"dep:hyper-util\",\"hyper-util?/client-legacy\",\"dep:tower\",\"tower?/balance\",\"tower?/buffer\",\"tower?/discover\",\"tower?/limit\",\"tower?/load-shed\",\"tower?/util\",\"dep:tokio\",\"tokio?/time\",\"dep:hyper-timeout\"],\"codegen\":[\"dep:async-trait\"],\"default\":[\"router\",\"transport\",\"codegen\"],\"deflate\":[\"dep:flate2\"],\"gzip\":[\"dep:flate2\"],\"router\":[\"dep:axum\",\"dep:tower\",\"tower?/util\"],\"server\":[\"dep:h2\",\"dep:hyper\",\"hyper?/server\",\"dep:hyper-util\",\"hyper-util?/service\",\"hyper-util?/server-auto\",\"dep:socket2\",\"dep:tokio\",\"tokio?/macros\",\"tokio?/net\",\"tokio?/time\",\"tokio-stream/net\",\"dep:tower\",\"tower?/util\",\"tower?/limit\",\"tower?/load-shed\"],\"tls-aws-lc\":[\"_tls-any\",\"tokio-rustls/aws-lc-rs\"],\"tls-connect-info\":[\"dep:tokio-rustls\"],\"tls-native-roots\":[\"_tls-any\",\"channel\",\"dep:rustls-native-certs\"],\"tls-ring\":[\"_tls-any\",\"tokio-rustls/ring\"],\"tls-webpki-roots\":[\"_tls-any\",\"channel\",\"dep:webpki-roots\"],\"transport\":[\"server\",\"channel\"],\"zstd\":[\"dep:zstd\"]}}", + "tower-http_0.6.8": "{\"dependencies\":[{\"features\":[\"tokio\"],\"name\":\"async-compression\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bitflags\",\"req\":\"^2.0.2\"},{\"kind\":\"dev\",\"name\":\"brotli\",\"req\":\"^8\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.14\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.14\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"http-range-header\",\"optional\":true,\"req\":\"^0.4.0\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"client-legacy\",\"http1\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"iri-string\",\"optional\":true,\"req\":\"^0.7.0\"},{\"default_features\":false,\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.17\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.1.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"sync_wrapper\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.6\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"tower\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"buffer\",\"util\",\"retry\",\"make\",\"timeout\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5\"},{\"name\":\"tower-layer\",\"req\":\"^0.3.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"zstd\",\"req\":\"^0.13\"}],\"features\":{\"add-extension\":[],\"auth\":[\"base64\",\"validate-request\"],\"catch-panic\":[\"tracing\",\"futures-util/std\",\"dep:http-body\",\"dep:http-body-util\"],\"compression-br\":[\"async-compression/brotli\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"compression-deflate\":[\"async-compression/zlib\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"compression-full\":[\"compression-br\",\"compression-deflate\",\"compression-gzip\",\"compression-zstd\"],\"compression-gzip\":[\"async-compression/gzip\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"compression-zstd\":[\"async-compression/zstd\",\"futures-core\",\"dep:http-body\",\"tokio-util\",\"tokio\"],\"cors\":[],\"decompression-br\":[\"async-compression/brotli\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"decompression-deflate\":[\"async-compression/zlib\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"decompression-full\":[\"decompression-br\",\"decompression-deflate\",\"decompression-gzip\",\"decompression-zstd\"],\"decompression-gzip\":[\"async-compression/gzip\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"decompression-zstd\":[\"async-compression/zstd\",\"futures-core\",\"dep:http-body\",\"dep:http-body-util\",\"tokio-util\",\"tokio\"],\"default\":[],\"follow-redirect\":[\"futures-util\",\"dep:http-body\",\"iri-string\",\"tower/util\"],\"fs\":[\"futures-core\",\"futures-util\",\"dep:http-body\",\"dep:http-body-util\",\"tokio/fs\",\"tokio-util/io\",\"tokio/io-util\",\"dep:http-range-header\",\"mime_guess\",\"mime\",\"percent-encoding\",\"httpdate\",\"set-status\",\"futures-util/alloc\",\"tracing\"],\"full\":[\"add-extension\",\"auth\",\"catch-panic\",\"compression-full\",\"cors\",\"decompression-full\",\"follow-redirect\",\"fs\",\"limit\",\"map-request-body\",\"map-response-body\",\"metrics\",\"normalize-path\",\"propagate-header\",\"redirect\",\"request-id\",\"sensitive-headers\",\"set-header\",\"set-status\",\"timeout\",\"trace\",\"util\",\"validate-request\"],\"limit\":[\"dep:http-body\",\"dep:http-body-util\"],\"map-request-body\":[],\"map-response-body\":[],\"metrics\":[\"dep:http-body\",\"tokio/time\"],\"normalize-path\":[],\"propagate-header\":[],\"redirect\":[],\"request-id\":[\"uuid\"],\"sensitive-headers\":[],\"set-header\":[],\"set-status\":[],\"timeout\":[\"dep:http-body\",\"tokio/time\"],\"trace\":[\"dep:http-body\",\"tracing\"],\"util\":[\"tower\"],\"validate-request\":[\"mime\"]}}", "tower-layer_0.3.3": "{\"dependencies\":[],\"features\":{}}", "tower-service_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.22\"},{\"kind\":\"dev\",\"name\":\"http\",\"req\":\"^0.2\"},{\"features\":[\"macros\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"tower-layer\",\"req\":\"^0.3\"}],\"features\":{}}", - "tower_0.5.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.22\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.22\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.22\"},{\"default_features\":false,\"name\":\"hdrhistogram\",\"optional\":true,\"req\":\"^7.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"hdrhistogram\",\"req\":\"^7.0\"},{\"kind\":\"dev\",\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.2\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4.0\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"slab\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"sync_wrapper\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.6.2\"},{\"features\":[\"macros\",\"sync\",\"test-util\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.6.2\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"tower-layer\",\"req\":\"^0.3.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3.3\"},{\"kind\":\"dev\",\"name\":\"tower-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.2\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.2\"},{\"default_features\":false,\"features\":[\"fmt\",\"ansi\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"}],\"features\":{\"__common\":[\"futures-core\",\"pin-project-lite\"],\"balance\":[\"discover\",\"load\",\"ready-cache\",\"make\",\"slab\",\"util\"],\"buffer\":[\"__common\",\"tokio/sync\",\"tokio/rt\",\"tokio-util\",\"tracing\"],\"discover\":[\"__common\"],\"filter\":[\"__common\",\"futures-util\"],\"full\":[\"balance\",\"buffer\",\"discover\",\"filter\",\"hedge\",\"limit\",\"load\",\"load-shed\",\"make\",\"ready-cache\",\"reconnect\",\"retry\",\"spawn-ready\",\"steer\",\"timeout\",\"util\"],\"hedge\":[\"util\",\"filter\",\"futures-util\",\"hdrhistogram\",\"tokio/time\",\"tracing\"],\"limit\":[\"__common\",\"tokio/time\",\"tokio/sync\",\"tokio-util\",\"tracing\"],\"load\":[\"__common\",\"tokio/time\",\"tracing\"],\"load-shed\":[\"__common\"],\"log\":[\"tracing/log\"],\"make\":[\"futures-util\",\"pin-project-lite\",\"tokio/io-std\"],\"ready-cache\":[\"futures-core\",\"futures-util\",\"indexmap\",\"tokio/sync\",\"tracing\",\"pin-project-lite\"],\"reconnect\":[\"make\",\"tokio/io-std\",\"tracing\"],\"retry\":[\"__common\",\"tokio/time\",\"util\"],\"spawn-ready\":[\"__common\",\"futures-util\",\"tokio/sync\",\"tokio/rt\",\"util\",\"tracing\"],\"steer\":[],\"timeout\":[\"pin-project-lite\",\"tokio/time\"],\"util\":[\"__common\",\"futures-util\",\"pin-project-lite\",\"sync_wrapper\"]}}", - "tracing-appender_0.2.3": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"name\":\"crossbeam-channel\",\"req\":\"^0.5.6\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"thiserror\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"formatting\",\"parsing\"],\"name\":\"time\",\"req\":\"^0.3.2\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"default_features\":false,\"features\":[\"fmt\",\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.18\"}],\"features\":{}}", + "tower_0.5.3": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.22\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.22\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.22\"},{\"default_features\":false,\"features\":[\"async-await-macro\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.22\"},{\"default_features\":false,\"name\":\"hdrhistogram\",\"optional\":true,\"req\":\"^7.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"hdrhistogram\",\"req\":\"^7.0\"},{\"kind\":\"dev\",\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.2\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"name\":\"slab\",\"optional\":true,\"req\":\"^0.4.9\"},{\"name\":\"sync_wrapper\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.6.2\"},{\"features\":[\"macros\",\"sync\",\"test-util\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"tower-layer\",\"req\":\"^0.3.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3.3\"},{\"kind\":\"dev\",\"name\":\"tower-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.2\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.2\"},{\"default_features\":false,\"features\":[\"fmt\",\"ansi\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"}],\"features\":{\"balance\":[\"discover\",\"load\",\"ready-cache\",\"make\",\"slab\",\"util\"],\"buffer\":[\"tokio/sync\",\"tokio/rt\",\"tokio-util\",\"tracing\",\"pin-project-lite\"],\"discover\":[\"futures-core\",\"pin-project-lite\"],\"filter\":[\"futures-util\",\"pin-project-lite\"],\"full\":[\"balance\",\"buffer\",\"discover\",\"filter\",\"hedge\",\"limit\",\"load\",\"load-shed\",\"make\",\"ready-cache\",\"reconnect\",\"retry\",\"spawn-ready\",\"steer\",\"timeout\",\"util\"],\"hedge\":[\"util\",\"filter\",\"futures-util\",\"hdrhistogram\",\"tokio/time\",\"tracing\"],\"limit\":[\"tokio/time\",\"tokio/sync\",\"tokio-util\",\"tracing\",\"pin-project-lite\"],\"load\":[\"tokio/time\",\"tracing\",\"pin-project-lite\"],\"load-shed\":[\"pin-project-lite\"],\"log\":[\"tracing/log\"],\"make\":[\"pin-project-lite\",\"tokio\"],\"ready-cache\":[\"futures-core\",\"futures-util\",\"indexmap\",\"tokio/sync\",\"tracing\",\"pin-project-lite\"],\"reconnect\":[\"make\",\"tracing\"],\"retry\":[\"tokio/time\",\"util\"],\"spawn-ready\":[\"futures-util\",\"tokio/sync\",\"tokio/rt\",\"util\",\"tracing\"],\"steer\":[],\"timeout\":[\"pin-project-lite\",\"tokio/time\"],\"tokio-stream\":[],\"util\":[\"futures-core\",\"futures-util\",\"pin-project-lite\",\"sync_wrapper\"]}}", + "tracing-appender_0.2.4": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"name\":\"crossbeam-channel\",\"req\":\"^0.5.6\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"formatting\",\"parsing\"],\"name\":\"time\",\"req\":\"^0.3.2\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"default_features\":false,\"features\":[\"fmt\",\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.18\"}],\"features\":{}}", "tracing-attributes_0.1.31": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1.67\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1.0.20\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"full\",\"parsing\",\"printing\",\"visit-mut\",\"clone-impls\",\"extra-traits\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"features\":[\"env-filter\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.64\"}],\"features\":{\"async-await\":[]}}", "tracing-core_0.1.36": "{\"dependencies\":[{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.13.0\"},{\"default_features\":false,\"name\":\"valuable\",\"optional\":true,\"req\":\"^0.1.0\",\"target\":\"cfg(tracing_unstable)\"}],\"features\":{\"default\":[\"std\",\"valuable?/std\"],\"std\":[\"once_cell\"]}}", "tracing-error_0.2.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"default_features\":false,\"features\":[\"registry\",\"fmt\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"}],\"features\":{\"default\":[\"traced-error\"],\"traced-error\":[]}}", "tracing-log_0.2.0": "{\"dependencies\":[{\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.7.6\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"name\":\"log\",\"req\":\"^0.4.17\"},{\"name\":\"lru\",\"optional\":true,\"req\":\"^0.7.7\"},{\"name\":\"once_cell\",\"req\":\"^1.13.0\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"name\":\"tracing-core\",\"req\":\"^0.1.28\"}],\"features\":{\"default\":[\"log-tracer\",\"std\"],\"interest-cache\":[\"lru\",\"ahash\"],\"log-tracer\":[],\"std\":[\"log/std\"]}}", - "tracing-opentelemetry_0.32.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-trait\",\"req\":\"^0.1.56\"},{\"default_features\":false,\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.17\"},{\"name\":\"js-sys\",\"req\":\"^0.3.64\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\"},{\"name\":\"lazy_static\",\"optional\":true,\"req\":\"^1.0.2\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry\",\"req\":\"^0.31.0\"},{\"features\":[\"trace\",\"metrics\"],\"kind\":\"dev\",\"name\":\"opentelemetry\",\"req\":\"^0.31.0\"},{\"features\":[\"metrics\",\"grpc-tonic\"],\"kind\":\"dev\",\"name\":\"opentelemetry-otlp\",\"req\":\"^0.31.0\"},{\"features\":[\"semconv_experimental\"],\"kind\":\"dev\",\"name\":\"opentelemetry-semantic-conventions\",\"req\":\"^0.31.0\"},{\"features\":[\"trace\",\"metrics\"],\"kind\":\"dev\",\"name\":\"opentelemetry-stdout\",\"req\":\"^0.31.0\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31.0\"},{\"default_features\":false,\"features\":[\"trace\",\"rt-tokio\",\"experimental_metrics_custom_reader\",\"testing\"],\"kind\":\"dev\",\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31.0\"},{\"features\":[\"flamegraph\",\"criterion\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.15.0\",\"target\":\"cfg(not(target_os = \\\"windows\\\"))\"},{\"name\":\"rustversion\",\"req\":\"^1.0.9\"},{\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-stream\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"default_features\":false,\"features\":[\"std\",\"attributes\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"name\":\"tracing-core\",\"req\":\"^0.1.28\"},{\"kind\":\"dev\",\"name\":\"tracing-error\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"tracing-log\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"registry\",\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"registry\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"name\":\"web-time\",\"req\":\"^1.0.0\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\"}],\"features\":{\"default\":[\"tracing-log\",\"metrics\"],\"metrics\":[\"opentelemetry/metrics\",\"opentelemetry_sdk/metrics\",\"smallvec\"]}}", + "tracing-opentelemetry_0.32.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"js-sys\",\"req\":\"^0.3.64\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\"},{\"name\":\"lazy_static\",\"optional\":true,\"req\":\"^1.0.2\"},{\"default_features\":false,\"features\":[\"trace\"],\"name\":\"opentelemetry\",\"req\":\"^0.31.0\"},{\"features\":[\"trace\",\"metrics\"],\"kind\":\"dev\",\"name\":\"opentelemetry\",\"req\":\"^0.31.0\"},{\"features\":[\"metrics\",\"grpc-tonic\"],\"kind\":\"dev\",\"name\":\"opentelemetry-otlp\",\"req\":\"^0.31.0\"},{\"features\":[\"semconv_experimental\"],\"kind\":\"dev\",\"name\":\"opentelemetry-semantic-conventions\",\"req\":\"^0.31.0\"},{\"features\":[\"trace\",\"metrics\"],\"kind\":\"dev\",\"name\":\"opentelemetry-stdout\",\"req\":\"^0.31.0\"},{\"default_features\":false,\"features\":[\"trace\",\"experimental_metrics_custom_reader\",\"testing\"],\"kind\":\"dev\",\"name\":\"opentelemetry_sdk\",\"req\":\"^0.31.0\"},{\"features\":[\"flamegraph\",\"criterion\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.15.0\",\"target\":\"cfg(not(target_os = \\\"windows\\\"))\"},{\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"default_features\":false,\"features\":[\"std\",\"attributes\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"name\":\"tracing-core\",\"req\":\"^0.1.28\"},{\"kind\":\"dev\",\"name\":\"tracing-error\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"tracing-log\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"registry\",\"std\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3.22\"},{\"default_features\":false,\"features\":[\"registry\",\"std\",\"fmt\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.0\"},{\"name\":\"web-time\",\"req\":\"^1.0.0\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\"}],\"features\":{\"default\":[\"tracing-log\",\"metrics\"],\"metrics\":[\"opentelemetry/metrics\",\"smallvec\"]}}", "tracing-subscriber_0.3.22": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"clock\",\"std\"],\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.26\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.17\"},{\"name\":\"matchers\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"nu-ansi-term\",\"optional\":true,\"req\":\"^0.50.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.13.0\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.1\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex-automata\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.140\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.82\"},{\"name\":\"sharded-slab\",\"optional\":true,\"req\":\"^0.1.4\"},{\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.9.0\"},{\"name\":\"thread_local\",\"optional\":true,\"req\":\"^1.1.4\"},{\"features\":[\"formatting\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.2\"},{\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.2\"},{\"features\":[\"rt\",\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.43\"},{\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1.43\"},{\"default_features\":false,\"name\":\"tracing-core\",\"req\":\"^0.1.35\"},{\"default_features\":false,\"features\":[\"std-future\",\"std\"],\"kind\":\"dev\",\"name\":\"tracing-futures\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"log-tracer\",\"std\"],\"name\":\"tracing-log\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"tracing-log\",\"req\":\"^0.2.0\"},{\"name\":\"tracing-serde\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"valuable-serde\",\"optional\":true,\"req\":\"^0.1.0\",\"target\":\"cfg(tracing_unstable)\"},{\"default_features\":false,\"name\":\"valuable_crate\",\"optional\":true,\"package\":\"valuable\",\"req\":\"^0.1.0\",\"target\":\"cfg(tracing_unstable)\"}],\"features\":{\"alloc\":[],\"ansi\":[\"fmt\",\"nu-ansi-term\"],\"default\":[\"smallvec\",\"fmt\",\"ansi\",\"tracing-log\",\"std\"],\"env-filter\":[\"matchers\",\"once_cell\",\"tracing\",\"std\",\"thread_local\",\"dep:regex-automata\"],\"fmt\":[\"registry\",\"std\"],\"json\":[\"tracing-serde\",\"serde\",\"serde_json\"],\"local-time\":[\"time/local-offset\"],\"nu-ansi-term\":[\"dep:nu-ansi-term\"],\"regex\":[],\"registry\":[\"sharded-slab\",\"thread_local\",\"std\"],\"std\":[\"alloc\",\"tracing-core/std\"],\"valuable\":[\"tracing-core/valuable\",\"valuable_crate\",\"valuable-serde\",\"tracing-serde/valuable\"]}}", "tracing-test-macro_0.2.5": "{\"dependencies\":[{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{\"no-env-filter\":[]}}", "tracing-test_0.2.5": "{\"dependencies\":[{\"features\":[\"rt-multi-thread\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"tracing\",\"req\":\"^0.1\"},{\"name\":\"tracing-core\",\"req\":\"^0.1\"},{\"features\":[\"env-filter\"],\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"name\":\"tracing-test-macro\",\"req\":\"^0.2.5\"}],\"features\":{\"no-env-filter\":[\"tracing-test-macro/no-env-filter\"]}}", "tracing_0.1.44": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.21\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.17\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.9\"},{\"name\":\"tracing-attributes\",\"optional\":true,\"req\":\"^0.1.31\"},{\"default_features\":false,\"name\":\"tracing-core\",\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.38\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"}],\"features\":{\"async-await\":[],\"attributes\":[\"tracing-attributes\"],\"default\":[\"std\",\"attributes\"],\"log-always\":[\"log\"],\"max_level_debug\":[],\"max_level_error\":[],\"max_level_info\":[],\"max_level_off\":[],\"max_level_trace\":[],\"max_level_warn\":[],\"release_max_level_debug\":[],\"release_max_level_error\":[],\"release_max_level_info\":[],\"release_max_level_off\":[],\"release_max_level_trace\":[],\"release_max_level_warn\":[],\"std\":[\"tracing-core/std\"],\"valuable\":[\"tracing-core/valuable\"]}}", - "tree-sitter-bash_0.25.0": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"tree-sitter\",\"req\":\"^0.25\"},{\"name\":\"tree-sitter-language\",\"req\":\"^0.1\"}],\"features\":{}}", + "tree-sitter-bash_0.25.1": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"tree-sitter\",\"req\":\"^0.25\"},{\"name\":\"tree-sitter-language\",\"req\":\"^0.1\"}],\"features\":{}}", "tree-sitter-highlight_0.25.10": "{\"dependencies\":[{\"name\":\"regex\",\"req\":\"^1.11.1\"},{\"name\":\"streaming-iterator\",\"req\":\"^0.1.9\"},{\"name\":\"thiserror\",\"req\":\"^2.0.11\"},{\"name\":\"tree-sitter\",\"req\":\"^0.25.10\"}],\"features\":{}}", - "tree-sitter-language_0.1.5": "{\"dependencies\":[],\"features\":{}}", + "tree-sitter-language_0.1.7": "{\"dependencies\":[],\"features\":{}}", "tree-sitter_0.25.10": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.71.1\"},{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.2.10\"},{\"default_features\":false,\"features\":[\"unicode\"],\"name\":\"regex\",\"req\":\"^1.11.1\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"req\":\"^0.8.5\"},{\"features\":[\"preserve_order\"],\"kind\":\"build\",\"name\":\"serde_json\",\"req\":\"^1.0.137\"},{\"name\":\"streaming-iterator\",\"req\":\"^0.1.9\"},{\"name\":\"tree-sitter-language\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"cranelift\",\"gc-drc\"],\"name\":\"wasmtime-c-api\",\"optional\":true,\"package\":\"wasmtime-c-api-impl\",\"req\":\"^29.0.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"regex/std\",\"regex/perf\",\"regex-syntax/unicode\"],\"wasm\":[\"std\",\"wasmtime-c-api\"]}}", - "tree_magic_mini_3.2.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.0\"},{\"name\":\"memchr\",\"req\":\"^2.0\"},{\"name\":\"nom\",\"req\":\"^7.0\"},{\"name\":\"once_cell\",\"req\":\"^1.0\"},{\"name\":\"petgraph\",\"req\":\"^0.6.0\"},{\"name\":\"tree_magic_db\",\"optional\":true,\"req\":\"^3.0\"}],\"features\":{\"with-gpl-data\":[\"dep:tree_magic_db\"]}}", + "tree_magic_mini_3.2.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.0\"},{\"name\":\"memchr\",\"req\":\"^2.0\"},{\"name\":\"nom\",\"req\":\"^8.0\"},{\"default_features\":false,\"name\":\"petgraph\",\"req\":\"^0.8.0\"},{\"name\":\"tree_magic_db\",\"optional\":true,\"req\":\"^3.0\"}],\"features\":{\"with-gpl-data\":[\"dep:tree_magic_db\"]}}", "try-lock_0.2.5": "{\"dependencies\":[],\"features\":{}}", "ts-rs-macros_11.1.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.28\"},{\"name\":\"termcolor\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"no-serde-warnings\":[],\"serde-compat\":[\"termcolor\"]}}", "ts-rs_11.1.0": "{\"dependencies\":[{\"features\":[\"serde\"],\"name\":\"bigdecimal\",\"optional\":true,\"req\":\">=0.0.13, <0.5\"},{\"name\":\"bson\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"bytes\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4\"},{\"name\":\"dprint-plugin-typescript\",\"optional\":true,\"req\":\"=0.95\"},{\"name\":\"heapless\",\"optional\":true,\"req\":\">=0.7, <0.9\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"ordered-float\",\"optional\":true,\"req\":\">=3, <6\"},{\"name\":\"semver\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"name\":\"smol_str\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"sync\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.40\"},{\"name\":\"ts-rs-macros\",\"req\":\"=11.1.0\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2\"},{\"name\":\"uuid\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"bigdecimal-impl\":[\"bigdecimal\"],\"bson-uuid-impl\":[\"bson\"],\"bytes-impl\":[\"bytes\"],\"chrono-impl\":[\"chrono\"],\"default\":[\"serde-compat\"],\"format\":[\"dprint-plugin-typescript\"],\"heapless-impl\":[\"heapless\"],\"import-esm\":[],\"indexmap-impl\":[\"indexmap\"],\"no-serde-warnings\":[\"ts-rs-macros/no-serde-warnings\"],\"ordered-float-impl\":[\"ordered-float\"],\"semver-impl\":[\"semver\"],\"serde-compat\":[\"ts-rs-macros/serde-compat\"],\"serde-json-impl\":[\"serde_json\"],\"smol_str-impl\":[\"smol_str\"],\"tokio-impl\":[\"tokio\"],\"url-impl\":[\"url\"],\"uuid-impl\":[\"uuid\"]}}", - "typenum_1.18.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"scale-info\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"const-generics\":[],\"force_unix_path_separator\":[],\"i128\":[],\"no_std\":[],\"scale_info\":[\"scale-info/derive\"],\"strict\":[]}}", + "type-map_0.5.1": "{\"dependencies\":[{\"name\":\"rustc-hash\",\"req\":\"^2\"}],\"features\":{}}", + "typenum_1.19.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"scale-info\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"const-generics\":[],\"force_unix_path_separator\":[],\"i128\":[],\"no_std\":[],\"scale_info\":[\"scale-info/derive\"],\"strict\":[]}}", "uds_windows_1.1.0": "{\"dependencies\":[{\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"name\":\"tempfile\",\"req\":\"^3\",\"target\":\"cfg(windows)\"},{\"features\":[\"winsock2\",\"ws2def\",\"minwinbase\",\"ntdef\",\"processthreadsapi\",\"handleapi\",\"ws2tcpip\",\"winbase\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "uname_0.1.1": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"}],\"features\":{}}", "unarray_0.1.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"test-strategy\",\"req\":\"^0.2\"}],\"features\":{}}", - "unicase_2.8.1": "{\"dependencies\":[],\"features\":{\"nightly\":[]}}", + "unic-langid-impl_0.9.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"tinystr\",\"req\":\"^0.8.0\"}],\"features\":{\"binary\":[\"serde\",\"serde_json\"],\"likelysubtags\":[]}}", + "unic-langid_0.9.6": "{\"dependencies\":[{\"name\":\"unic-langid-impl\",\"req\":\"^0.9.6\"},{\"name\":\"unic-langid-macros\",\"optional\":true,\"req\":\"^0.9.6\"},{\"kind\":\"dev\",\"name\":\"unic-langid-macros\",\"req\":\"^0.9.6\"}],\"features\":{\"default\":[],\"likelysubtags\":[\"unic-langid-impl/likelysubtags\"],\"macros\":[\"unic-langid-macros\"],\"serde\":[\"unic-langid-impl/serde\"]}}", + "unicase_2.9.0": "{\"dependencies\":[],\"features\":{\"nightly\":[]}}", "unicode-bidi_0.3.18": "{\"dependencies\":[{\"name\":\"flame\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"flamer\",\"optional\":true,\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\">=0.8, <2.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\">=0.8, <2.0\"},{\"features\":[\"union\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\">=1.13\"}],\"features\":{\"bench_it\":[],\"default\":[\"std\",\"hardcoded-data\"],\"flame_it\":[\"flame\",\"flamer\"],\"hardcoded-data\":[],\"std\":[],\"unstable\":[],\"with_serde\":[\"serde\"]}}", - "unicode-ident_1.0.18": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"fst\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"roaring\",\"req\":\"^0.10\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ucd-trie\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"unicode-xid\",\"req\":\"^0.2.6\"}],\"features\":{}}", + "unicode-ident_1.0.22": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"fst\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"roaring\",\"req\":\"^0.11\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ucd-trie\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"unicode-xid\",\"req\":\"^0.2.6\"}],\"features\":{}}", "unicode-linebreak_0.1.5": "{\"dependencies\":[],\"features\":{}}", "unicode-normalization_0.1.25": "{\"dependencies\":[{\"features\":[\"alloc\"],\"name\":\"tinyvec\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "unicode-properties_0.1.4": "{\"dependencies\":[],\"features\":{\"default\":[\"general-category\",\"emoji\"],\"emoji\":[],\"general-category\":[]}}", @@ -1264,16 +1320,17 @@ "unicode-width_0.1.14": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"}],\"features\":{\"cjk\":[],\"default\":[\"cjk\"],\"no_std\":[],\"rustc-dep-of-std\":[\"std\",\"core\",\"compiler_builtins\"]}}", "unicode-width_0.2.1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"std\",\"optional\":true,\"package\":\"rustc-std-workspace-std\",\"req\":\"^1.0\"}],\"features\":{\"cjk\":[],\"default\":[\"cjk\"],\"no_std\":[],\"rustc-dep-of-std\":[\"std\",\"core\"]}}", "unicode-xid_0.2.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"}],\"features\":{\"bench\":[],\"default\":[],\"no_std\":[]}}", + "universal-hash_0.5.1": "{\"dependencies\":[{\"name\":\"crypto-common\",\"req\":\"^0.1.6\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.4\"}],\"features\":{\"std\":[\"crypto-common/std\"]}}", "unsafe-libyaml_0.2.11": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.0\"}],\"features\":{}}", "untrusted_0.9.0": "{\"dependencies\":[],\"features\":{}}", "ureq-proto_0.5.3": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"std\"],\"name\":\"base64\",\"req\":\"^0.22.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"http\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"name\":\"httparse\",\"req\":\"^1.8.0\"},{\"name\":\"log\",\"req\":\"^0.4.22\"}],\"features\":{\"client\":[],\"default\":[\"client\",\"server\"],\"server\":[]}}", "ureq_3.1.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_no_alloc\",\"req\":\"^1.1.2\"},{\"kind\":\"dev\",\"name\":\"auto-args\",\"req\":\"^0.3.0\"},{\"name\":\"base64\",\"req\":\"^0.22.1\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"default_features\":false,\"features\":[\"preserve_order\"],\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.22\"},{\"default_features\":false,\"features\":[\"pem\",\"std\"],\"name\":\"der\",\"optional\":true,\"req\":\"^0.7.9\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8.34\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11.7\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0.30\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.2.15\"},{\"name\":\"log\",\"req\":\"^0.4.25\"},{\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0.5\"},{\"default_features\":false,\"name\":\"native-tls\",\"optional\":true,\"req\":\"^0.2.12\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3.1\"},{\"default_features\":false,\"features\":[\"logging\",\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.22\"},{\"features\":[\"aws-lc-rs\"],\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"^1.11.0\"},{\"default_features\":false,\"name\":\"rustls-platform-verifier\",\"optional\":true,\"req\":\"^0.6.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.138\"},{\"features\":[\"std\",\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.204\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.120\"},{\"name\":\"socks\",\"optional\":true,\"req\":\"^0.3.4\"},{\"default_features\":false,\"features\":[\"client\"],\"name\":\"ureq-proto\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"name\":\"url\",\"optional\":true,\"req\":\"^2.3.1\"},{\"name\":\"utf-8\",\"req\":\"^0.7.6\"},{\"default_features\":false,\"name\":\"webpki-root-certs\",\"optional\":true,\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"_doc\":[\"rustls?/aws-lc-rs\"],\"_ring\":[\"rustls?/ring\"],\"_rustls\":[],\"_test\":[],\"_tls\":[\"dep:rustls-pki-types\"],\"_url\":[\"dep:url\"],\"brotli\":[\"dep:brotli-decompressor\"],\"charset\":[\"dep:encoding_rs\"],\"cookies\":[\"dep:cookie_store\",\"_url\"],\"default\":[\"rustls\",\"gzip\"],\"gzip\":[\"dep:flate2\"],\"json\":[\"dep:serde\",\"dep:serde_json\",\"cookie_store?/serde_json\"],\"multipart\":[\"dep:mime_guess\",\"dep:getrandom\"],\"native-tls\":[\"dep:native-tls\",\"dep:der\",\"_tls\",\"dep:webpki-root-certs\"],\"platform-verifier\":[\"dep:rustls-platform-verifier\"],\"rustls\":[\"rustls-no-provider\",\"_ring\"],\"rustls-no-provider\":[\"dep:rustls\",\"_tls\",\"dep:webpki-roots\",\"_rustls\"],\"socks-proxy\":[\"dep:socks\"],\"vendored\":[\"native-tls?/vendored\"]}}", - "url_2.5.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"form_urlencoded\",\"req\":\"^1.2.1\"},{\"default_features\":false,\"features\":[\"alloc\",\"compiled_data\"],\"name\":\"idna\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"percent-encoding\",\"req\":\"^2.3.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"}],\"features\":{\"debugger_visualizer\":[],\"default\":[\"std\"],\"expose_internals\":[],\"std\":[\"idna/std\",\"percent-encoding/std\",\"form_urlencoded/std\"]}}", + "url_2.5.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"form_urlencoded\",\"req\":\"^1.2.2\"},{\"default_features\":false,\"features\":[\"alloc\",\"compiled_data\"],\"name\":\"idna\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"percent-encoding\",\"req\":\"^2.3.2\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"}],\"features\":{\"debugger_visualizer\":[],\"default\":[\"std\"],\"expose_internals\":[],\"serde\":[\"dep:serde\",\"dep:serde_derive\"],\"std\":[\"idna/std\",\"percent-encoding/std\",\"form_urlencoded/std\",\"serde?/std\"]}}", "urlencoding_2.1.3": "{\"dependencies\":[],\"features\":{}}", "utf-8_0.7.6": "{\"dependencies\":[],\"features\":{}}", "utf8_iter_1.0.4": "{\"dependencies\":[],\"features\":{}}", "utf8parse_0.2.2": "{\"dependencies\":[],\"features\":{\"default\":[],\"nightly\":[]}}", - "uuid_1.18.1": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"atomic\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"borsh-derive\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.18.1\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"md-5\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.56\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.79\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.56\"},{\"default_features\":false,\"name\":\"sha1_smol\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"slog\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.52\"},{\"name\":\"uuid-macro-internal\",\"optional\":true,\"req\":\"^1.18.1\"},{\"name\":\"uuid-rng-internal-lib\",\"optional\":true,\"package\":\"uuid-rng-internal\",\"req\":\"^1.18.1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"default_features\":false,\"features\":[\"msrv\"],\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"features\":[\"derive\"],\"name\":\"zerocopy\",\"optional\":true,\"req\":\"^0.8\"}],\"features\":{\"atomic\":[\"dep:atomic\"],\"borsh\":[\"dep:borsh\",\"dep:borsh-derive\"],\"default\":[\"std\"],\"fast-rng\":[\"rng\",\"dep:rand\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"macro-diagnostics\":[\"dep:uuid-macro-internal\"],\"md5\":[\"dep:md-5\"],\"rng\":[\"dep:getrandom\"],\"rng-getrandom\":[\"rng\",\"dep:getrandom\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/getrandom\"],\"rng-rand\":[\"rng\",\"dep:rand\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/rand\"],\"sha1\":[\"dep:sha1_smol\"],\"std\":[\"wasm-bindgen?/std\",\"js-sys?/std\"],\"v1\":[\"atomic\"],\"v3\":[\"md5\"],\"v4\":[\"rng\"],\"v5\":[\"sha1\"],\"v6\":[\"atomic\"],\"v7\":[\"rng\"],\"v8\":[]}}", + "uuid_1.20.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.1.3\"},{\"default_features\":false,\"name\":\"atomic\",\"optional\":true,\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"borsh-derive\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.20.0\"},{\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"default_features\":false,\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"), target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"name\":\"md-5\",\"optional\":true,\"req\":\"^0.10\"},{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\",\"target\":\"cfg(not(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\"))))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0.56\"},{\"default_features\":false,\"name\":\"sha1_smol\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"slog\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.52\"},{\"name\":\"uuid-rng-internal-lib\",\"optional\":true,\"package\":\"uuid-rng-internal\",\"req\":\"^1.20.0\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"default_features\":false,\"features\":[\"msrv\"],\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", any(target_os = \\\"unknown\\\", target_os = \\\"none\\\")))\"},{\"features\":[\"derive\"],\"name\":\"zerocopy\",\"optional\":true,\"req\":\"^0.8\"}],\"features\":{\"atomic\":[\"dep:atomic\"],\"borsh\":[\"dep:borsh\",\"dep:borsh-derive\"],\"default\":[\"std\"],\"fast-rng\":[\"rng\",\"dep:rand\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"macro-diagnostics\":[],\"md5\":[\"dep:md-5\"],\"rng\":[\"dep:getrandom\"],\"rng-getrandom\":[\"rng\",\"dep:getrandom\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/getrandom\"],\"rng-rand\":[\"rng\",\"dep:rand\",\"uuid-rng-internal-lib\",\"uuid-rng-internal-lib/rand\"],\"serde\":[\"dep:serde_core\"],\"sha1\":[\"dep:sha1_smol\"],\"std\":[\"wasm-bindgen?/std\",\"js-sys?/std\"],\"v1\":[\"atomic\"],\"v3\":[\"md5\"],\"v4\":[\"rng\"],\"v5\":[\"sha1\"],\"v6\":[\"atomic\"],\"v7\":[\"rng\"],\"v8\":[]}}", "valuable_0.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"valuable-derive\",\"optional\":true,\"req\":\"=0.1.1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"derive\":[\"valuable-derive\"],\"std\":[\"alloc\"]}}", "vcpkg_0.2.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3.7\"}],\"features\":{}}", "version_check_0.9.5": "{\"dependencies\":[],\"features\":{}}", @@ -1283,131 +1340,134 @@ "walkdir_2.5.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"same-file\",\"req\":\"^1.0.1\"},{\"name\":\"winapi-util\",\"req\":\"^0.1.1\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "want_0.3.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"tokio-executor\",\"req\":\"^0.2.0-alpha.2\"},{\"kind\":\"dev\",\"name\":\"tokio-sync\",\"req\":\"^0.2.0-alpha.2\"},{\"name\":\"try-lock\",\"req\":\"^0.2.4\"}],\"features\":{}}", "wasi_0.11.1+wasi-snapshot-preview1": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\"],\"std\":[]}}", - "wasi_0.14.2+wasi-0.2.4": "{\"dependencies\":[{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"bitflags\"],\"name\":\"wit-bindgen-rt\",\"req\":\"^0.39.0\"}],\"features\":{\"default\":[\"std\"],\"rustc-dep-of-std\":[\"compiler_builtins\",\"core\",\"rustc-std-workspace-alloc\"],\"std\":[]}}", + "wasip2_1.0.2+wasi-0.2.9": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"wit-bindgen\",\"req\":\"^0.51.0\"}],\"features\":{\"bitflags\":[\"wit-bindgen/bitflags\"],\"default\":[\"std\",\"bitflags\"],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"wit-bindgen/rustc-dep-of-std\"],\"std\":[]}}", "wasite_0.1.0": "{\"dependencies\":[],\"features\":{}}", - "wasm-bindgen-backend_0.2.100": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.0.0\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"wasm-bindgen-shared\",\"req\":\"=0.2.100\"}],\"features\":{\"extra-traits\":[\"syn/extra-traits\"]}}", - "wasm-bindgen-futures_0.4.50": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.8\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"js-sys\",\"req\":\"=0.3.77\"},{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.100\"},{\"default_features\":false,\"features\":[\"MessageEvent\",\"Worker\"],\"name\":\"web-sys\",\"req\":\"=0.3.77\",\"target\":\"cfg(target_feature = \\\"atomics\\\")\"}],\"features\":{\"default\":[\"std\"],\"futures-core-03-stream\":[\"futures-core\"],\"std\":[\"wasm-bindgen/std\",\"js-sys/std\",\"web-sys/std\"]}}", - "wasm-bindgen-macro-support_0.2.100": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"visit\",\"visit-mut\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"wasm-bindgen-backend\",\"req\":\"=0.2.100\"},{\"name\":\"wasm-bindgen-shared\",\"req\":\"=0.2.100\"}],\"features\":{\"extra-traits\":[\"syn/extra-traits\"],\"strict-macro\":[]}}", - "wasm-bindgen-macro_0.2.100": "{\"dependencies\":[{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"name\":\"wasm-bindgen-macro-support\",\"req\":\"=0.2.100\"}],\"features\":{\"strict-macro\":[\"wasm-bindgen-macro-support/strict-macro\"],\"xxx_debug_only_print_generated_code\":[]}}", - "wasm-bindgen-shared_0.2.100": "{\"dependencies\":[{\"name\":\"unicode-ident\",\"req\":\"^1.0.5\"}],\"features\":{}}", - "wasm-bindgen_0.2.100": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"wasm-bindgen-macro\",\"req\":\"=0.2.100\"}],\"features\":{\"default\":[\"std\",\"msrv\"],\"enable-interning\":[\"std\"],\"gg-alloc\":[],\"msrv\":[\"rustversion\"],\"serde-serialize\":[\"serde\",\"serde_json\",\"std\"],\"spans\":[],\"std\":[],\"strict-macro\":[\"wasm-bindgen-macro/strict-macro\"],\"xxx_debug_only_print_generated_code\":[\"wasm-bindgen-macro/xxx_debug_only_print_generated_code\"]}}", + "wasm-bindgen-futures_0.4.58": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.8\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-lite\",\"req\":\"^2\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"js-sys\",\"req\":\"=0.3.85\"},{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.108\"},{\"default_features\":false,\"features\":[\"MessageEvent\",\"Worker\"],\"name\":\"web-sys\",\"req\":\"=0.3.85\",\"target\":\"cfg(target_feature = \\\"atomics\\\")\"}],\"features\":{\"default\":[\"std\"],\"futures-core-03-stream\":[\"futures-core\"],\"std\":[\"wasm-bindgen/std\",\"js-sys/std\",\"web-sys/std\",\"futures-util\"]}}", + "wasm-bindgen-macro-support_0.2.108": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.0.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"visit\",\"visit-mut\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"wasm-bindgen-shared\",\"req\":\"=0.2.108\"}],\"features\":{\"extra-traits\":[\"syn/extra-traits\"],\"strict-macro\":[]}}", + "wasm-bindgen-macro_0.2.108": "{\"dependencies\":[{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"},{\"name\":\"wasm-bindgen-macro-support\",\"req\":\"=0.2.108\"}],\"features\":{\"strict-macro\":[\"wasm-bindgen-macro-support/strict-macro\"]}}", + "wasm-bindgen-shared_0.2.108": "{\"dependencies\":[{\"name\":\"unicode-ident\",\"req\":\"^1.0.5\"}],\"features\":{}}", + "wasm-bindgen_0.2.108": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"build\",\"name\":\"rustversion-compat\",\"package\":\"rustversion\",\"req\":\"^1.0.6\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"wasm-bindgen-macro\",\"req\":\"=0.2.108\"},{\"name\":\"wasm-bindgen-shared\",\"req\":\"=0.2.108\"}],\"features\":{\"default\":[\"std\"],\"enable-interning\":[\"std\"],\"gg-alloc\":[],\"msrv\":[],\"rustversion\":[],\"serde-serialize\":[\"serde\",\"serde_json\",\"std\"],\"spans\":[],\"std\":[],\"strict-macro\":[\"wasm-bindgen-macro/strict-macro\"],\"xxx_debug_only_print_generated_code\":[]}}", "wasm-streams_0.4.2": "{\"dependencies\":[{\"features\":[\"io\",\"sink\"],\"name\":\"futures-util\",\"req\":\"^0.3.31\"},{\"features\":[\"futures\"],\"kind\":\"dev\",\"name\":\"gloo-timers\",\"req\":\"^0.3.0\"},{\"name\":\"js-sys\",\"req\":\"^0.3.72\"},{\"kind\":\"dev\",\"name\":\"pin-project\",\"req\":\"^1\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.95\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.45\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.45\"},{\"features\":[\"AbortSignal\",\"QueuingStrategy\",\"ReadableStream\",\"ReadableStreamType\",\"ReadableWritablePair\",\"ReadableStreamByobReader\",\"ReadableStreamReaderMode\",\"ReadableStreamReadResult\",\"ReadableStreamByobRequest\",\"ReadableStreamDefaultReader\",\"ReadableByteStreamController\",\"ReadableStreamGetReaderOptions\",\"ReadableStreamDefaultController\",\"StreamPipeOptions\",\"TransformStream\",\"TransformStreamDefaultController\",\"Transformer\",\"UnderlyingSink\",\"UnderlyingSource\",\"WritableStream\",\"WritableStreamDefaultController\",\"WritableStreamDefaultWriter\"],\"name\":\"web-sys\",\"req\":\"^0.3.72\"},{\"features\":[\"console\",\"AbortSignal\",\"ErrorEvent\",\"PromiseRejectionEvent\",\"Response\",\"ReadableStream\",\"Window\"],\"kind\":\"dev\",\"name\":\"web-sys\",\"req\":\"^0.3.72\"}],\"features\":{}}", - "wayland-backend_0.3.11": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"concat-idents\",\"req\":\"^1.1\"},{\"name\":\"downcast-rs\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"raw-window-handle\",\"optional\":true,\"req\":\"^0.5.0\"},{\"features\":[\"event\",\"fs\",\"net\",\"process\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"name\":\"rwh_06\",\"optional\":true,\"package\":\"raw-window-handle\",\"req\":\"^0.6.0\"},{\"name\":\"scoped-tls\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"scoped-tls\",\"req\":\"^1.0\"},{\"features\":[\"union\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.9\"},{\"name\":\"wayland-sys\",\"req\":\"^0.31.7\"}],\"features\":{\"client_system\":[\"wayland-sys/client\",\"dep:scoped-tls\"],\"dlopen\":[\"wayland-sys/dlopen\"],\"server_system\":[\"wayland-sys/server\",\"dep:scoped-tls\"]}}", - "wayland-client_0.31.11": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3.16\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"event\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.11\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.7\"}],\"features\":{}}", - "wayland-protocols-wlr_0.3.9": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.11\"},{\"name\":\"wayland-client\",\"optional\":true,\"req\":\"^0.31.11\"},{\"name\":\"wayland-protocols\",\"req\":\"^0.32.9\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.7\"},{\"name\":\"wayland-server\",\"optional\":true,\"req\":\"^0.31.10\"}],\"features\":{\"client\":[\"wayland-client\",\"wayland-protocols/client\"],\"server\":[\"wayland-server\",\"wayland-protocols/server\"]}}", - "wayland-protocols_0.32.9": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.11\"},{\"name\":\"wayland-client\",\"optional\":true,\"req\":\"^0.31.11\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.7\"},{\"name\":\"wayland-server\",\"optional\":true,\"req\":\"^0.31.10\"}],\"features\":{\"client\":[\"wayland-client\"],\"server\":[\"wayland-server\"],\"staging\":[],\"unstable\":[]}}", - "wayland-scanner_0.31.7": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.11\"},{\"name\":\"quick-xml\",\"req\":\"^0.37.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2\"}],\"features\":{}}", - "wayland-sys_0.31.7": "{\"dependencies\":[{\"name\":\"dlib\",\"optional\":true,\"req\":\"^0.5.1\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"memoffset\",\"optional\":true,\"req\":\"^0.9\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.7\"}],\"features\":{\"client\":[\"dep:dlib\",\"dep:log\"],\"cursor\":[\"client\"],\"dlopen\":[\"once_cell\"],\"egl\":[\"client\"],\"server\":[\"libc\",\"memoffset\",\"dep:dlib\",\"dep:log\"]}}", - "web-sys_0.3.77": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"js-sys\",\"req\":\"=0.3.77\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.100\"}],\"features\":{\"AbortController\":[],\"AbortSignal\":[\"EventTarget\"],\"AddEventListenerOptions\":[],\"AesCbcParams\":[],\"AesCtrParams\":[],\"AesDerivedKeyParams\":[],\"AesGcmParams\":[],\"AesKeyAlgorithm\":[],\"AesKeyGenParams\":[],\"Algorithm\":[],\"AlignSetting\":[],\"AllowedBluetoothDevice\":[],\"AllowedUsbDevice\":[],\"AlphaOption\":[],\"AnalyserNode\":[\"AudioNode\",\"EventTarget\"],\"AnalyserOptions\":[],\"AngleInstancedArrays\":[],\"Animation\":[\"EventTarget\"],\"AnimationEffect\":[],\"AnimationEvent\":[\"Event\"],\"AnimationEventInit\":[],\"AnimationPlayState\":[],\"AnimationPlaybackEvent\":[\"Event\"],\"AnimationPlaybackEventInit\":[],\"AnimationPropertyDetails\":[],\"AnimationPropertyValueDetails\":[],\"AnimationTimeline\":[],\"AssignedNodesOptions\":[],\"AttestationConveyancePreference\":[],\"Attr\":[\"EventTarget\",\"Node\"],\"AttributeNameValue\":[],\"AudioBuffer\":[],\"AudioBufferOptions\":[],\"AudioBufferSourceNode\":[\"AudioNode\",\"AudioScheduledSourceNode\",\"EventTarget\"],\"AudioBufferSourceOptions\":[],\"AudioConfiguration\":[],\"AudioContext\":[\"BaseAudioContext\",\"EventTarget\"],\"AudioContextLatencyCategory\":[],\"AudioContextOptions\":[],\"AudioContextState\":[],\"AudioData\":[],\"AudioDataCopyToOptions\":[],\"AudioDataInit\":[],\"AudioDecoder\":[],\"AudioDecoderConfig\":[],\"AudioDecoderInit\":[],\"AudioDecoderSupport\":[],\"AudioDestinationNode\":[\"AudioNode\",\"EventTarget\"],\"AudioEncoder\":[],\"AudioEncoderConfig\":[],\"AudioEncoderInit\":[],\"AudioEncoderSupport\":[],\"AudioListener\":[],\"AudioNode\":[\"EventTarget\"],\"AudioNodeOptions\":[],\"AudioParam\":[],\"AudioParamMap\":[],\"AudioProcessingEvent\":[\"Event\"],\"AudioSampleFormat\":[],\"AudioScheduledSourceNode\":[\"AudioNode\",\"EventTarget\"],\"AudioSinkInfo\":[],\"AudioSinkOptions\":[],\"AudioSinkType\":[],\"AudioStreamTrack\":[\"EventTarget\",\"MediaStreamTrack\"],\"AudioTrack\":[],\"AudioTrackList\":[\"EventTarget\"],\"AudioWorklet\":[\"Worklet\"],\"AudioWorkletGlobalScope\":[\"WorkletGlobalScope\"],\"AudioWorkletNode\":[\"AudioNode\",\"EventTarget\"],\"AudioWorkletNodeOptions\":[],\"AudioWorkletProcessor\":[],\"AuthenticationExtensionsClientInputs\":[],\"AuthenticationExtensionsClientInputsJson\":[],\"AuthenticationExtensionsClientOutputs\":[],\"AuthenticationExtensionsClientOutputsJson\":[],\"AuthenticationExtensionsDevicePublicKeyInputs\":[],\"AuthenticationExtensionsDevicePublicKeyOutputs\":[],\"AuthenticationExtensionsLargeBlobInputs\":[],\"AuthenticationExtensionsLargeBlobOutputs\":[],\"AuthenticationExtensionsPrfInputs\":[],\"AuthenticationExtensionsPrfOutputs\":[],\"AuthenticationExtensionsPrfValues\":[],\"AuthenticationResponseJson\":[],\"AuthenticatorAssertionResponse\":[\"AuthenticatorResponse\"],\"AuthenticatorAssertionResponseJson\":[],\"AuthenticatorAttachment\":[],\"AuthenticatorAttestationResponse\":[\"AuthenticatorResponse\"],\"AuthenticatorAttestationResponseJson\":[],\"AuthenticatorResponse\":[],\"AuthenticatorSelectionCriteria\":[],\"AuthenticatorTransport\":[],\"AutoKeyword\":[],\"AutocompleteInfo\":[],\"BarProp\":[],\"BaseAudioContext\":[\"EventTarget\"],\"BaseComputedKeyframe\":[],\"BaseKeyframe\":[],\"BasePropertyIndexedKeyframe\":[],\"BasicCardRequest\":[],\"BasicCardResponse\":[],\"BasicCardType\":[],\"BatteryManager\":[\"EventTarget\"],\"BeforeUnloadEvent\":[\"Event\"],\"BinaryType\":[],\"BiquadFilterNode\":[\"AudioNode\",\"EventTarget\"],\"BiquadFilterOptions\":[],\"BiquadFilterType\":[],\"Blob\":[],\"BlobEvent\":[\"Event\"],\"BlobEventInit\":[],\"BlobPropertyBag\":[],\"BlockParsingOptions\":[],\"Bluetooth\":[\"EventTarget\"],\"BluetoothAdvertisingEvent\":[\"Event\"],\"BluetoothAdvertisingEventInit\":[],\"BluetoothCharacteristicProperties\":[],\"BluetoothDataFilterInit\":[],\"BluetoothDevice\":[\"EventTarget\"],\"BluetoothLeScanFilterInit\":[],\"BluetoothManufacturerDataMap\":[],\"BluetoothPermissionDescriptor\":[],\"BluetoothPermissionResult\":[\"EventTarget\",\"PermissionStatus\"],\"BluetoothPermissionStorage\":[],\"BluetoothRemoteGattCharacteristic\":[\"EventTarget\"],\"BluetoothRemoteGattDescriptor\":[],\"BluetoothRemoteGattServer\":[],\"BluetoothRemoteGattService\":[\"EventTarget\"],\"BluetoothServiceDataMap\":[],\"BluetoothUuid\":[],\"BoxQuadOptions\":[],\"BroadcastChannel\":[\"EventTarget\"],\"BrowserElementDownloadOptions\":[],\"BrowserElementExecuteScriptOptions\":[],\"BrowserFeedWriter\":[],\"BrowserFindCaseSensitivity\":[],\"BrowserFindDirection\":[],\"ByteLengthQueuingStrategy\":[],\"Cache\":[],\"CacheBatchOperation\":[],\"CacheQueryOptions\":[],\"CacheStorage\":[],\"CacheStorageNamespace\":[],\"CanvasCaptureMediaStream\":[\"EventTarget\",\"MediaStream\"],\"CanvasCaptureMediaStreamTrack\":[\"EventTarget\",\"MediaStreamTrack\"],\"CanvasGradient\":[],\"CanvasPattern\":[],\"CanvasRenderingContext2d\":[],\"CanvasWindingRule\":[],\"CaretChangedReason\":[],\"CaretPosition\":[],\"CaretStateChangedEventInit\":[],\"CdataSection\":[\"CharacterData\",\"EventTarget\",\"Node\",\"Text\"],\"ChannelCountMode\":[],\"ChannelInterpretation\":[],\"ChannelMergerNode\":[\"AudioNode\",\"EventTarget\"],\"ChannelMergerOptions\":[],\"ChannelSplitterNode\":[\"AudioNode\",\"EventTarget\"],\"ChannelSplitterOptions\":[],\"CharacterData\":[\"EventTarget\",\"Node\"],\"CheckerboardReason\":[],\"CheckerboardReport\":[],\"CheckerboardReportService\":[],\"ChromeFilePropertyBag\":[],\"ChromeWorker\":[\"EventTarget\",\"Worker\"],\"Client\":[],\"ClientQueryOptions\":[],\"ClientRectsAndTexts\":[],\"ClientType\":[],\"Clients\":[],\"Clipboard\":[\"EventTarget\"],\"ClipboardEvent\":[\"Event\"],\"ClipboardEventInit\":[],\"ClipboardItem\":[],\"ClipboardItemOptions\":[],\"ClipboardPermissionDescriptor\":[],\"ClipboardUnsanitizedFormats\":[],\"CloseEvent\":[\"Event\"],\"CloseEventInit\":[],\"CodecState\":[],\"CollectedClientData\":[],\"ColorSpaceConversion\":[],\"Comment\":[\"CharacterData\",\"EventTarget\",\"Node\"],\"CompositeOperation\":[],\"CompositionEvent\":[\"Event\",\"UiEvent\"],\"CompositionEventInit\":[],\"CompressionFormat\":[],\"CompressionStream\":[],\"ComputedEffectTiming\":[],\"ConnStatusDict\":[],\"ConnectionType\":[],\"ConsoleCounter\":[],\"ConsoleCounterError\":[],\"ConsoleEvent\":[],\"ConsoleInstance\":[],\"ConsoleInstanceOptions\":[],\"ConsoleLevel\":[],\"ConsoleLogLevel\":[],\"ConsoleProfileEvent\":[],\"ConsoleStackEntry\":[],\"ConsoleTimerError\":[],\"ConsoleTimerLogOrEnd\":[],\"ConsoleTimerStart\":[],\"ConstantSourceNode\":[\"AudioNode\",\"AudioScheduledSourceNode\",\"EventTarget\"],\"ConstantSourceOptions\":[],\"ConstrainBooleanParameters\":[],\"ConstrainDomStringParameters\":[],\"ConstrainDoubleRange\":[],\"ConstrainLongRange\":[],\"ContextAttributes2d\":[],\"ConvertCoordinateOptions\":[],\"ConvolverNode\":[\"AudioNode\",\"EventTarget\"],\"ConvolverOptions\":[],\"Coordinates\":[],\"CountQueuingStrategy\":[],\"Credential\":[],\"CredentialCreationOptions\":[],\"CredentialPropertiesOutput\":[],\"CredentialRequestOptions\":[],\"CredentialsContainer\":[],\"Crypto\":[],\"CryptoKey\":[],\"CryptoKeyPair\":[],\"CssAnimation\":[\"Animation\",\"EventTarget\"],\"CssBoxType\":[],\"CssConditionRule\":[\"CssGroupingRule\",\"CssRule\"],\"CssCounterStyleRule\":[\"CssRule\"],\"CssFontFaceRule\":[\"CssRule\"],\"CssFontFeatureValuesRule\":[\"CssRule\"],\"CssGroupingRule\":[\"CssRule\"],\"CssImportRule\":[\"CssRule\"],\"CssKeyframeRule\":[\"CssRule\"],\"CssKeyframesRule\":[\"CssRule\"],\"CssMediaRule\":[\"CssConditionRule\",\"CssGroupingRule\",\"CssRule\"],\"CssNamespaceRule\":[\"CssRule\"],\"CssPageRule\":[\"CssRule\"],\"CssPseudoElement\":[],\"CssRule\":[],\"CssRuleList\":[],\"CssStyleDeclaration\":[],\"CssStyleRule\":[\"CssRule\"],\"CssStyleSheet\":[\"StyleSheet\"],\"CssStyleSheetParsingMode\":[],\"CssSupportsRule\":[\"CssConditionRule\",\"CssGroupingRule\",\"CssRule\"],\"CssTransition\":[\"Animation\",\"EventTarget\"],\"CustomElementRegistry\":[],\"CustomEvent\":[\"Event\"],\"CustomEventInit\":[],\"DataTransfer\":[],\"DataTransferItem\":[],\"DataTransferItemList\":[],\"DateTimeValue\":[],\"DecoderDoctorNotification\":[],\"DecoderDoctorNotificationType\":[],\"DecompressionStream\":[],\"DedicatedWorkerGlobalScope\":[\"EventTarget\",\"WorkerGlobalScope\"],\"DelayNode\":[\"AudioNode\",\"EventTarget\"],\"DelayOptions\":[],\"DeviceAcceleration\":[],\"DeviceAccelerationInit\":[],\"DeviceLightEvent\":[\"Event\"],\"DeviceLightEventInit\":[],\"DeviceMotionEvent\":[\"Event\"],\"DeviceMotionEventInit\":[],\"DeviceOrientationEvent\":[\"Event\"],\"DeviceOrientationEventInit\":[],\"DeviceProximityEvent\":[\"Event\"],\"DeviceProximityEventInit\":[],\"DeviceRotationRate\":[],\"DeviceRotationRateInit\":[],\"DhKeyDeriveParams\":[],\"DirectionSetting\":[],\"Directory\":[],\"DirectoryPickerOptions\":[],\"DisplayMediaStreamConstraints\":[],\"DisplayNameOptions\":[],\"DisplayNameResult\":[],\"DistanceModelType\":[],\"DnsCacheDict\":[],\"DnsCacheEntry\":[],\"DnsLookupDict\":[],\"Document\":[\"EventTarget\",\"Node\"],\"DocumentFragment\":[\"EventTarget\",\"Node\"],\"DocumentTimeline\":[\"AnimationTimeline\"],\"DocumentTimelineOptions\":[],\"DocumentType\":[\"EventTarget\",\"Node\"],\"DomError\":[],\"DomException\":[],\"DomImplementation\":[],\"DomMatrix\":[\"DomMatrixReadOnly\"],\"DomMatrix2dInit\":[],\"DomMatrixInit\":[],\"DomMatrixReadOnly\":[],\"DomParser\":[],\"DomPoint\":[\"DomPointReadOnly\"],\"DomPointInit\":[],\"DomPointReadOnly\":[],\"DomQuad\":[],\"DomQuadInit\":[],\"DomQuadJson\":[],\"DomRect\":[\"DomRectReadOnly\"],\"DomRectInit\":[],\"DomRectList\":[],\"DomRectReadOnly\":[],\"DomRequest\":[\"EventTarget\"],\"DomRequestReadyState\":[],\"DomStringList\":[],\"DomStringMap\":[],\"DomTokenList\":[],\"DomWindowResizeEventDetail\":[],\"DoubleRange\":[],\"DragEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"DragEventInit\":[],\"DynamicsCompressorNode\":[\"AudioNode\",\"EventTarget\"],\"DynamicsCompressorOptions\":[],\"EcKeyAlgorithm\":[],\"EcKeyGenParams\":[],\"EcKeyImportParams\":[],\"EcdhKeyDeriveParams\":[],\"EcdsaParams\":[],\"EffectTiming\":[],\"Element\":[\"EventTarget\",\"Node\"],\"ElementCreationOptions\":[],\"ElementDefinitionOptions\":[],\"EncodedAudioChunk\":[],\"EncodedAudioChunkInit\":[],\"EncodedAudioChunkMetadata\":[],\"EncodedAudioChunkType\":[],\"EncodedVideoChunk\":[],\"EncodedVideoChunkInit\":[],\"EncodedVideoChunkMetadata\":[],\"EncodedVideoChunkType\":[],\"EndingTypes\":[],\"ErrorCallback\":[],\"ErrorEvent\":[\"Event\"],\"ErrorEventInit\":[],\"Event\":[],\"EventInit\":[],\"EventListener\":[],\"EventListenerOptions\":[],\"EventModifierInit\":[],\"EventSource\":[\"EventTarget\"],\"EventSourceInit\":[],\"EventTarget\":[],\"Exception\":[],\"ExtBlendMinmax\":[],\"ExtColorBufferFloat\":[],\"ExtColorBufferHalfFloat\":[],\"ExtDisjointTimerQuery\":[],\"ExtFragDepth\":[],\"ExtSRgb\":[],\"ExtShaderTextureLod\":[],\"ExtTextureFilterAnisotropic\":[],\"ExtTextureNorm16\":[],\"ExtendableEvent\":[\"Event\"],\"ExtendableEventInit\":[],\"ExtendableMessageEvent\":[\"Event\",\"ExtendableEvent\"],\"ExtendableMessageEventInit\":[],\"External\":[],\"FakePluginMimeEntry\":[],\"FakePluginTagInit\":[],\"FetchEvent\":[\"Event\",\"ExtendableEvent\"],\"FetchEventInit\":[],\"FetchObserver\":[\"EventTarget\"],\"FetchReadableStreamReadDataArray\":[],\"FetchReadableStreamReadDataDone\":[],\"FetchState\":[],\"File\":[\"Blob\"],\"FileCallback\":[],\"FileList\":[],\"FilePickerAcceptType\":[],\"FilePickerOptions\":[],\"FilePropertyBag\":[],\"FileReader\":[\"EventTarget\"],\"FileReaderSync\":[],\"FileSystem\":[],\"FileSystemCreateWritableOptions\":[],\"FileSystemDirectoryEntry\":[\"FileSystemEntry\"],\"FileSystemDirectoryHandle\":[\"FileSystemHandle\"],\"FileSystemDirectoryReader\":[],\"FileSystemEntriesCallback\":[],\"FileSystemEntry\":[],\"FileSystemEntryCallback\":[],\"FileSystemFileEntry\":[\"FileSystemEntry\"],\"FileSystemFileHandle\":[\"FileSystemHandle\"],\"FileSystemFlags\":[],\"FileSystemGetDirectoryOptions\":[],\"FileSystemGetFileOptions\":[],\"FileSystemHandle\":[],\"FileSystemHandleKind\":[],\"FileSystemHandlePermissionDescriptor\":[],\"FileSystemPermissionDescriptor\":[],\"FileSystemPermissionMode\":[],\"FileSystemReadWriteOptions\":[],\"FileSystemRemoveOptions\":[],\"FileSystemSyncAccessHandle\":[],\"FileSystemWritableFileStream\":[\"WritableStream\"],\"FillMode\":[],\"FlashClassification\":[],\"FlowControlType\":[],\"FocusEvent\":[\"Event\",\"UiEvent\"],\"FocusEventInit\":[],\"FocusOptions\":[],\"FontData\":[],\"FontFace\":[],\"FontFaceDescriptors\":[],\"FontFaceLoadStatus\":[],\"FontFaceSet\":[\"EventTarget\"],\"FontFaceSetIterator\":[],\"FontFaceSetIteratorResult\":[],\"FontFaceSetLoadEvent\":[\"Event\"],\"FontFaceSetLoadEventInit\":[],\"FontFaceSetLoadStatus\":[],\"FormData\":[],\"FrameType\":[],\"FuzzingFunctions\":[],\"GainNode\":[\"AudioNode\",\"EventTarget\"],\"GainOptions\":[],\"Gamepad\":[],\"GamepadButton\":[],\"GamepadEffectParameters\":[],\"GamepadEvent\":[\"Event\"],\"GamepadEventInit\":[],\"GamepadHand\":[],\"GamepadHapticActuator\":[],\"GamepadHapticActuatorType\":[],\"GamepadHapticEffectType\":[],\"GamepadHapticsResult\":[],\"GamepadMappingType\":[],\"GamepadPose\":[],\"GamepadTouch\":[],\"Geolocation\":[],\"GetAnimationsOptions\":[],\"GetRootNodeOptions\":[],\"GetUserMediaRequest\":[],\"Gpu\":[],\"GpuAdapter\":[],\"GpuAdapterInfo\":[],\"GpuAddressMode\":[],\"GpuAutoLayoutMode\":[],\"GpuBindGroup\":[],\"GpuBindGroupDescriptor\":[],\"GpuBindGroupEntry\":[],\"GpuBindGroupLayout\":[],\"GpuBindGroupLayoutDescriptor\":[],\"GpuBindGroupLayoutEntry\":[],\"GpuBlendComponent\":[],\"GpuBlendFactor\":[],\"GpuBlendOperation\":[],\"GpuBlendState\":[],\"GpuBuffer\":[],\"GpuBufferBinding\":[],\"GpuBufferBindingLayout\":[],\"GpuBufferBindingType\":[],\"GpuBufferDescriptor\":[],\"GpuBufferMapState\":[],\"GpuCanvasAlphaMode\":[],\"GpuCanvasConfiguration\":[],\"GpuCanvasContext\":[],\"GpuCanvasToneMapping\":[],\"GpuCanvasToneMappingMode\":[],\"GpuColorDict\":[],\"GpuColorTargetState\":[],\"GpuCommandBuffer\":[],\"GpuCommandBufferDescriptor\":[],\"GpuCommandEncoder\":[],\"GpuCommandEncoderDescriptor\":[],\"GpuCompareFunction\":[],\"GpuCompilationInfo\":[],\"GpuCompilationMessage\":[],\"GpuCompilationMessageType\":[],\"GpuComputePassDescriptor\":[],\"GpuComputePassEncoder\":[],\"GpuComputePassTimestampWrites\":[],\"GpuComputePipeline\":[],\"GpuComputePipelineDescriptor\":[],\"GpuCopyExternalImageDestInfo\":[],\"GpuCopyExternalImageSourceInfo\":[],\"GpuCullMode\":[],\"GpuDepthStencilState\":[],\"GpuDevice\":[\"EventTarget\"],\"GpuDeviceDescriptor\":[],\"GpuDeviceLostInfo\":[],\"GpuDeviceLostReason\":[],\"GpuError\":[],\"GpuErrorFilter\":[],\"GpuExtent3dDict\":[],\"GpuExternalTexture\":[],\"GpuExternalTextureBindingLayout\":[],\"GpuExternalTextureDescriptor\":[],\"GpuFeatureName\":[],\"GpuFilterMode\":[],\"GpuFragmentState\":[],\"GpuFrontFace\":[],\"GpuIndexFormat\":[],\"GpuInternalError\":[\"GpuError\"],\"GpuLoadOp\":[],\"GpuMipmapFilterMode\":[],\"GpuMultisampleState\":[],\"GpuObjectDescriptorBase\":[],\"GpuOrigin2dDict\":[],\"GpuOrigin3dDict\":[],\"GpuOutOfMemoryError\":[\"GpuError\"],\"GpuPipelineDescriptorBase\":[],\"GpuPipelineError\":[\"DomException\"],\"GpuPipelineErrorInit\":[],\"GpuPipelineErrorReason\":[],\"GpuPipelineLayout\":[],\"GpuPipelineLayoutDescriptor\":[],\"GpuPowerPreference\":[],\"GpuPrimitiveState\":[],\"GpuPrimitiveTopology\":[],\"GpuProgrammableStage\":[],\"GpuQuerySet\":[],\"GpuQuerySetDescriptor\":[],\"GpuQueryType\":[],\"GpuQueue\":[],\"GpuQueueDescriptor\":[],\"GpuRenderBundle\":[],\"GpuRenderBundleDescriptor\":[],\"GpuRenderBundleEncoder\":[],\"GpuRenderBundleEncoderDescriptor\":[],\"GpuRenderPassColorAttachment\":[],\"GpuRenderPassDepthStencilAttachment\":[],\"GpuRenderPassDescriptor\":[],\"GpuRenderPassEncoder\":[],\"GpuRenderPassLayout\":[],\"GpuRenderPassTimestampWrites\":[],\"GpuRenderPipeline\":[],\"GpuRenderPipelineDescriptor\":[],\"GpuRequestAdapterOptions\":[],\"GpuSampler\":[],\"GpuSamplerBindingLayout\":[],\"GpuSamplerBindingType\":[],\"GpuSamplerDescriptor\":[],\"GpuShaderModule\":[],\"GpuShaderModuleCompilationHint\":[],\"GpuShaderModuleDescriptor\":[],\"GpuStencilFaceState\":[],\"GpuStencilOperation\":[],\"GpuStorageTextureAccess\":[],\"GpuStorageTextureBindingLayout\":[],\"GpuStoreOp\":[],\"GpuSupportedFeatures\":[],\"GpuSupportedLimits\":[],\"GpuTexelCopyBufferInfo\":[],\"GpuTexelCopyBufferLayout\":[],\"GpuTexelCopyTextureInfo\":[],\"GpuTexture\":[],\"GpuTextureAspect\":[],\"GpuTextureBindingLayout\":[],\"GpuTextureDescriptor\":[],\"GpuTextureDimension\":[],\"GpuTextureFormat\":[],\"GpuTextureSampleType\":[],\"GpuTextureView\":[],\"GpuTextureViewDescriptor\":[],\"GpuTextureViewDimension\":[],\"GpuUncapturedErrorEvent\":[\"Event\"],\"GpuUncapturedErrorEventInit\":[],\"GpuValidationError\":[\"GpuError\"],\"GpuVertexAttribute\":[],\"GpuVertexBufferLayout\":[],\"GpuVertexFormat\":[],\"GpuVertexState\":[],\"GpuVertexStepMode\":[],\"GroupedHistoryEventInit\":[],\"HalfOpenInfoDict\":[],\"HardwareAcceleration\":[],\"HashChangeEvent\":[\"Event\"],\"HashChangeEventInit\":[],\"Headers\":[],\"HeadersGuardEnum\":[],\"Hid\":[\"EventTarget\"],\"HidCollectionInfo\":[],\"HidConnectionEvent\":[\"Event\"],\"HidConnectionEventInit\":[],\"HidDevice\":[\"EventTarget\"],\"HidDeviceFilter\":[],\"HidDeviceRequestOptions\":[],\"HidInputReportEvent\":[\"Event\"],\"HidInputReportEventInit\":[],\"HidReportInfo\":[],\"HidReportItem\":[],\"HidUnitSystem\":[],\"HiddenPluginEventInit\":[],\"History\":[],\"HitRegionOptions\":[],\"HkdfParams\":[],\"HmacDerivedKeyParams\":[],\"HmacImportParams\":[],\"HmacKeyAlgorithm\":[],\"HmacKeyGenParams\":[],\"HtmlAllCollection\":[],\"HtmlAnchorElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlAreaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlAudioElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"HtmlMediaElement\",\"Node\"],\"HtmlBaseElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlBodyElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlBrElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlButtonElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlCanvasElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlCollection\":[],\"HtmlDListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDataElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDataListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDetailsElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDialogElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDirectoryElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDivElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDocument\":[\"Document\",\"EventTarget\",\"Node\"],\"HtmlElement\":[\"Element\",\"EventTarget\",\"Node\"],\"HtmlEmbedElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFieldSetElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFontElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFormControlsCollection\":[\"HtmlCollection\"],\"HtmlFormElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFrameElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFrameSetElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHeadElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHeadingElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHrElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHtmlElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlIFrameElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlImageElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlInputElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLabelElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLegendElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLiElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLinkElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMapElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMediaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMenuElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMenuItemElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMetaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMeterElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlModElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlObjectElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOptGroupElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOptionElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOptionsCollection\":[\"HtmlCollection\"],\"HtmlOutputElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlParagraphElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlParamElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlPictureElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlPreElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlProgressElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlQuoteElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlScriptElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSelectElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSlotElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSourceElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSpanElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlStyleElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableCaptionElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableCellElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableColElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableRowElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableSectionElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTemplateElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTextAreaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTimeElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTitleElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTrackElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlUListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlUnknownElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlVideoElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"HtmlMediaElement\",\"Node\"],\"HttpConnDict\":[],\"HttpConnInfo\":[],\"HttpConnectionElement\":[],\"IdbCursor\":[],\"IdbCursorDirection\":[],\"IdbCursorWithValue\":[\"IdbCursor\"],\"IdbDatabase\":[\"EventTarget\"],\"IdbFactory\":[],\"IdbFileHandle\":[\"EventTarget\"],\"IdbFileMetadataParameters\":[],\"IdbFileRequest\":[\"DomRequest\",\"EventTarget\"],\"IdbIndex\":[],\"IdbIndexParameters\":[],\"IdbKeyRange\":[],\"IdbLocaleAwareKeyRange\":[\"IdbKeyRange\"],\"IdbMutableFile\":[\"EventTarget\"],\"IdbObjectStore\":[],\"IdbObjectStoreParameters\":[],\"IdbOpenDbOptions\":[],\"IdbOpenDbRequest\":[\"EventTarget\",\"IdbRequest\"],\"IdbRequest\":[\"EventTarget\"],\"IdbRequestReadyState\":[],\"IdbTransaction\":[\"EventTarget\"],\"IdbTransactionDurability\":[],\"IdbTransactionMode\":[],\"IdbTransactionOptions\":[],\"IdbVersionChangeEvent\":[\"Event\"],\"IdbVersionChangeEventInit\":[],\"IdleDeadline\":[],\"IdleRequestOptions\":[],\"IirFilterNode\":[\"AudioNode\",\"EventTarget\"],\"IirFilterOptions\":[],\"ImageBitmap\":[],\"ImageBitmapOptions\":[],\"ImageBitmapRenderingContext\":[],\"ImageCapture\":[],\"ImageCaptureError\":[],\"ImageCaptureErrorEvent\":[\"Event\"],\"ImageCaptureErrorEventInit\":[],\"ImageData\":[],\"ImageDecodeOptions\":[],\"ImageDecodeResult\":[],\"ImageDecoder\":[],\"ImageDecoderInit\":[],\"ImageEncodeOptions\":[],\"ImageOrientation\":[],\"ImageTrack\":[\"EventTarget\"],\"ImageTrackList\":[],\"InputDeviceInfo\":[\"MediaDeviceInfo\"],\"InputEvent\":[\"Event\",\"UiEvent\"],\"InputEventInit\":[],\"IntersectionObserver\":[],\"IntersectionObserverEntry\":[],\"IntersectionObserverEntryInit\":[],\"IntersectionObserverInit\":[],\"IntlUtils\":[],\"IsInputPendingOptions\":[],\"IterableKeyAndValueResult\":[],\"IterableKeyOrValueResult\":[],\"IterationCompositeOperation\":[],\"JsonWebKey\":[],\"KeyAlgorithm\":[],\"KeyEvent\":[],\"KeyFrameRequestEvent\":[\"Event\"],\"KeyIdsInitData\":[],\"KeyboardEvent\":[\"Event\",\"UiEvent\"],\"KeyboardEventInit\":[],\"KeyframeAnimationOptions\":[],\"KeyframeEffect\":[\"AnimationEffect\"],\"KeyframeEffectOptions\":[],\"L10nElement\":[],\"L10nValue\":[],\"LargeBlobSupport\":[],\"LatencyMode\":[],\"LifecycleCallbacks\":[],\"LineAlignSetting\":[],\"ListBoxObject\":[],\"LocalMediaStream\":[\"EventTarget\",\"MediaStream\"],\"LocaleInfo\":[],\"Location\":[],\"Lock\":[],\"LockInfo\":[],\"LockManager\":[],\"LockManagerSnapshot\":[],\"LockMode\":[],\"LockOptions\":[],\"MathMlElement\":[\"Element\",\"EventTarget\",\"Node\"],\"MediaCapabilities\":[],\"MediaCapabilitiesInfo\":[],\"MediaConfiguration\":[],\"MediaDecodingConfiguration\":[],\"MediaDecodingType\":[],\"MediaDeviceInfo\":[],\"MediaDeviceKind\":[],\"MediaDevices\":[\"EventTarget\"],\"MediaElementAudioSourceNode\":[\"AudioNode\",\"EventTarget\"],\"MediaElementAudioSourceOptions\":[],\"MediaEncodingConfiguration\":[],\"MediaEncodingType\":[],\"MediaEncryptedEvent\":[\"Event\"],\"MediaError\":[],\"MediaImage\":[],\"MediaKeyError\":[\"Event\"],\"MediaKeyMessageEvent\":[\"Event\"],\"MediaKeyMessageEventInit\":[],\"MediaKeyMessageType\":[],\"MediaKeyNeededEventInit\":[],\"MediaKeySession\":[\"EventTarget\"],\"MediaKeySessionType\":[],\"MediaKeyStatus\":[],\"MediaKeyStatusMap\":[],\"MediaKeySystemAccess\":[],\"MediaKeySystemConfiguration\":[],\"MediaKeySystemMediaCapability\":[],\"MediaKeySystemStatus\":[],\"MediaKeys\":[],\"MediaKeysPolicy\":[],\"MediaKeysRequirement\":[],\"MediaList\":[],\"MediaMetadata\":[],\"MediaMetadataInit\":[],\"MediaPositionState\":[],\"MediaQueryList\":[\"EventTarget\"],\"MediaQueryListEvent\":[\"Event\"],\"MediaQueryListEventInit\":[],\"MediaRecorder\":[\"EventTarget\"],\"MediaRecorderErrorEvent\":[\"Event\"],\"MediaRecorderErrorEventInit\":[],\"MediaRecorderOptions\":[],\"MediaSession\":[],\"MediaSessionAction\":[],\"MediaSessionActionDetails\":[],\"MediaSessionPlaybackState\":[],\"MediaSource\":[\"EventTarget\"],\"MediaSourceEndOfStreamError\":[],\"MediaSourceEnum\":[],\"MediaSourceReadyState\":[],\"MediaStream\":[\"EventTarget\"],\"MediaStreamAudioDestinationNode\":[\"AudioNode\",\"EventTarget\"],\"MediaStreamAudioSourceNode\":[\"AudioNode\",\"EventTarget\"],\"MediaStreamAudioSourceOptions\":[],\"MediaStreamConstraints\":[],\"MediaStreamError\":[],\"MediaStreamEvent\":[\"Event\"],\"MediaStreamEventInit\":[],\"MediaStreamTrack\":[\"EventTarget\"],\"MediaStreamTrackEvent\":[\"Event\"],\"MediaStreamTrackEventInit\":[],\"MediaStreamTrackGenerator\":[\"EventTarget\",\"MediaStreamTrack\"],\"MediaStreamTrackGeneratorInit\":[],\"MediaStreamTrackProcessor\":[],\"MediaStreamTrackProcessorInit\":[],\"MediaStreamTrackState\":[],\"MediaTrackCapabilities\":[],\"MediaTrackConstraintSet\":[],\"MediaTrackConstraints\":[],\"MediaTrackSettings\":[],\"MediaTrackSupportedConstraints\":[],\"MemoryAttribution\":[],\"MemoryAttributionContainer\":[],\"MemoryBreakdownEntry\":[],\"MemoryMeasurement\":[],\"MessageChannel\":[],\"MessageEvent\":[\"Event\"],\"MessageEventInit\":[],\"MessagePort\":[\"EventTarget\"],\"MidiAccess\":[\"EventTarget\"],\"MidiConnectionEvent\":[\"Event\"],\"MidiConnectionEventInit\":[],\"MidiInput\":[\"EventTarget\",\"MidiPort\"],\"MidiInputMap\":[],\"MidiMessageEvent\":[\"Event\"],\"MidiMessageEventInit\":[],\"MidiOptions\":[],\"MidiOutput\":[\"EventTarget\",\"MidiPort\"],\"MidiOutputMap\":[],\"MidiPort\":[\"EventTarget\"],\"MidiPortConnectionState\":[],\"MidiPortDeviceState\":[],\"MidiPortType\":[],\"MimeType\":[],\"MimeTypeArray\":[],\"MouseEvent\":[\"Event\",\"UiEvent\"],\"MouseEventInit\":[],\"MouseScrollEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"MozDebug\":[],\"MutationEvent\":[\"Event\"],\"MutationObserver\":[],\"MutationObserverInit\":[],\"MutationObservingInfo\":[],\"MutationRecord\":[],\"NamedNodeMap\":[],\"NativeOsFileReadOptions\":[],\"NativeOsFileWriteAtomicOptions\":[],\"NavigationType\":[],\"Navigator\":[],\"NavigatorAutomationInformation\":[],\"NavigatorUaBrandVersion\":[],\"NavigatorUaData\":[],\"NetworkCommandOptions\":[],\"NetworkInformation\":[\"EventTarget\"],\"NetworkResultOptions\":[],\"Node\":[\"EventTarget\"],\"NodeFilter\":[],\"NodeIterator\":[],\"NodeList\":[],\"Notification\":[\"EventTarget\"],\"NotificationAction\":[],\"NotificationDirection\":[],\"NotificationEvent\":[\"Event\",\"ExtendableEvent\"],\"NotificationEventInit\":[],\"NotificationOptions\":[],\"NotificationPermission\":[],\"ObserverCallback\":[],\"OesElementIndexUint\":[],\"OesStandardDerivatives\":[],\"OesTextureFloat\":[],\"OesTextureFloatLinear\":[],\"OesTextureHalfFloat\":[],\"OesTextureHalfFloatLinear\":[],\"OesVertexArrayObject\":[],\"OfflineAudioCompletionEvent\":[\"Event\"],\"OfflineAudioCompletionEventInit\":[],\"OfflineAudioContext\":[\"BaseAudioContext\",\"EventTarget\"],\"OfflineAudioContextOptions\":[],\"OfflineResourceList\":[\"EventTarget\"],\"OffscreenCanvas\":[\"EventTarget\"],\"OffscreenCanvasRenderingContext2d\":[],\"OpenFilePickerOptions\":[],\"OpenWindowEventDetail\":[],\"OptionalEffectTiming\":[],\"OrientationLockType\":[],\"OrientationType\":[],\"OscillatorNode\":[\"AudioNode\",\"AudioScheduledSourceNode\",\"EventTarget\"],\"OscillatorOptions\":[],\"OscillatorType\":[],\"OverSampleType\":[],\"OvrMultiview2\":[],\"PageTransitionEvent\":[\"Event\"],\"PageTransitionEventInit\":[],\"PaintRequest\":[],\"PaintRequestList\":[],\"PaintWorkletGlobalScope\":[\"WorkletGlobalScope\"],\"PannerNode\":[\"AudioNode\",\"EventTarget\"],\"PannerOptions\":[],\"PanningModelType\":[],\"ParityType\":[],\"Path2d\":[],\"PaymentAddress\":[],\"PaymentComplete\":[],\"PaymentMethodChangeEvent\":[\"Event\",\"PaymentRequestUpdateEvent\"],\"PaymentMethodChangeEventInit\":[],\"PaymentRequestUpdateEvent\":[\"Event\"],\"PaymentRequestUpdateEventInit\":[],\"PaymentResponse\":[],\"Pbkdf2Params\":[],\"PcImplIceConnectionState\":[],\"PcImplIceGatheringState\":[],\"PcImplSignalingState\":[],\"PcObserverStateType\":[],\"Performance\":[\"EventTarget\"],\"PerformanceEntry\":[],\"PerformanceEntryEventInit\":[],\"PerformanceEntryFilterOptions\":[],\"PerformanceMark\":[\"PerformanceEntry\"],\"PerformanceMeasure\":[\"PerformanceEntry\"],\"PerformanceNavigation\":[],\"PerformanceNavigationTiming\":[\"PerformanceEntry\",\"PerformanceResourceTiming\"],\"PerformanceObserver\":[],\"PerformanceObserverEntryList\":[],\"PerformanceObserverInit\":[],\"PerformanceResourceTiming\":[\"PerformanceEntry\"],\"PerformanceServerTiming\":[],\"PerformanceTiming\":[],\"PeriodicWave\":[],\"PeriodicWaveConstraints\":[],\"PeriodicWaveOptions\":[],\"PermissionDescriptor\":[],\"PermissionName\":[],\"PermissionState\":[],\"PermissionStatus\":[\"EventTarget\"],\"Permissions\":[],\"PlaneLayout\":[],\"PlaybackDirection\":[],\"Plugin\":[],\"PluginArray\":[],\"PluginCrashedEventInit\":[],\"PointerEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"PointerEventInit\":[],\"PopStateEvent\":[\"Event\"],\"PopStateEventInit\":[],\"PopupBlockedEvent\":[\"Event\"],\"PopupBlockedEventInit\":[],\"Position\":[],\"PositionAlignSetting\":[],\"PositionError\":[],\"PositionOptions\":[],\"PremultiplyAlpha\":[],\"Presentation\":[],\"PresentationAvailability\":[\"EventTarget\"],\"PresentationConnection\":[\"EventTarget\"],\"PresentationConnectionAvailableEvent\":[\"Event\"],\"PresentationConnectionAvailableEventInit\":[],\"PresentationConnectionBinaryType\":[],\"PresentationConnectionCloseEvent\":[\"Event\"],\"PresentationConnectionCloseEventInit\":[],\"PresentationConnectionClosedReason\":[],\"PresentationConnectionList\":[\"EventTarget\"],\"PresentationConnectionState\":[],\"PresentationReceiver\":[],\"PresentationRequest\":[\"EventTarget\"],\"PresentationStyle\":[],\"ProcessingInstruction\":[\"CharacterData\",\"EventTarget\",\"Node\"],\"ProfileTimelineLayerRect\":[],\"ProfileTimelineMarker\":[],\"ProfileTimelineMessagePortOperationType\":[],\"ProfileTimelineStackFrame\":[],\"ProfileTimelineWorkerOperationType\":[],\"ProgressEvent\":[\"Event\"],\"ProgressEventInit\":[],\"PromiseNativeHandler\":[],\"PromiseRejectionEvent\":[\"Event\"],\"PromiseRejectionEventInit\":[],\"PublicKeyCredential\":[\"Credential\"],\"PublicKeyCredentialCreationOptions\":[],\"PublicKeyCredentialCreationOptionsJson\":[],\"PublicKeyCredentialDescriptor\":[],\"PublicKeyCredentialDescriptorJson\":[],\"PublicKeyCredentialEntity\":[],\"PublicKeyCredentialHints\":[],\"PublicKeyCredentialParameters\":[],\"PublicKeyCredentialRequestOptions\":[],\"PublicKeyCredentialRequestOptionsJson\":[],\"PublicKeyCredentialRpEntity\":[],\"PublicKeyCredentialType\":[],\"PublicKeyCredentialUserEntity\":[],\"PublicKeyCredentialUserEntityJson\":[],\"PushEncryptionKeyName\":[],\"PushEvent\":[\"Event\",\"ExtendableEvent\"],\"PushEventInit\":[],\"PushManager\":[],\"PushMessageData\":[],\"PushPermissionState\":[],\"PushSubscription\":[],\"PushSubscriptionInit\":[],\"PushSubscriptionJson\":[],\"PushSubscriptionKeys\":[],\"PushSubscriptionOptions\":[],\"PushSubscriptionOptionsInit\":[],\"QueryOptions\":[],\"QueuingStrategy\":[],\"QueuingStrategyInit\":[],\"RadioNodeList\":[\"NodeList\"],\"Range\":[],\"RcwnPerfStats\":[],\"RcwnStatus\":[],\"ReadableByteStreamController\":[],\"ReadableStream\":[],\"ReadableStreamByobReader\":[],\"ReadableStreamByobRequest\":[],\"ReadableStreamDefaultController\":[],\"ReadableStreamDefaultReader\":[],\"ReadableStreamGetReaderOptions\":[],\"ReadableStreamIteratorOptions\":[],\"ReadableStreamReadResult\":[],\"ReadableStreamReaderMode\":[],\"ReadableStreamType\":[],\"ReadableWritablePair\":[],\"RecordingState\":[],\"ReferrerPolicy\":[],\"RegisterRequest\":[],\"RegisterResponse\":[],\"RegisteredKey\":[],\"RegistrationOptions\":[],\"RegistrationResponseJson\":[],\"Request\":[],\"RequestCache\":[],\"RequestCredentials\":[],\"RequestDestination\":[],\"RequestDeviceOptions\":[],\"RequestInit\":[],\"RequestMediaKeySystemAccessNotification\":[],\"RequestMode\":[],\"RequestRedirect\":[],\"ResidentKeyRequirement\":[],\"ResizeObserver\":[],\"ResizeObserverBoxOptions\":[],\"ResizeObserverEntry\":[],\"ResizeObserverOptions\":[],\"ResizeObserverSize\":[],\"ResizeQuality\":[],\"Response\":[],\"ResponseInit\":[],\"ResponseType\":[],\"RsaHashedImportParams\":[],\"RsaOaepParams\":[],\"RsaOtherPrimesInfo\":[],\"RsaPssParams\":[],\"RtcAnswerOptions\":[],\"RtcBundlePolicy\":[],\"RtcCertificate\":[],\"RtcCertificateExpiration\":[],\"RtcCodecStats\":[],\"RtcConfiguration\":[],\"RtcDataChannel\":[\"EventTarget\"],\"RtcDataChannelEvent\":[\"Event\"],\"RtcDataChannelEventInit\":[],\"RtcDataChannelInit\":[],\"RtcDataChannelState\":[],\"RtcDataChannelType\":[],\"RtcDegradationPreference\":[],\"RtcEncodedAudioFrame\":[],\"RtcEncodedAudioFrameMetadata\":[],\"RtcEncodedAudioFrameOptions\":[],\"RtcEncodedVideoFrame\":[],\"RtcEncodedVideoFrameMetadata\":[],\"RtcEncodedVideoFrameOptions\":[],\"RtcEncodedVideoFrameType\":[],\"RtcFecParameters\":[],\"RtcIceCandidate\":[],\"RtcIceCandidateInit\":[],\"RtcIceCandidatePairStats\":[],\"RtcIceCandidateStats\":[],\"RtcIceComponentStats\":[],\"RtcIceConnectionState\":[],\"RtcIceCredentialType\":[],\"RtcIceGatheringState\":[],\"RtcIceServer\":[],\"RtcIceTransportPolicy\":[],\"RtcIdentityAssertion\":[],\"RtcIdentityAssertionResult\":[],\"RtcIdentityProvider\":[],\"RtcIdentityProviderDetails\":[],\"RtcIdentityProviderOptions\":[],\"RtcIdentityProviderRegistrar\":[],\"RtcIdentityValidationResult\":[],\"RtcInboundRtpStreamStats\":[],\"RtcMediaStreamStats\":[],\"RtcMediaStreamTrackStats\":[],\"RtcOfferAnswerOptions\":[],\"RtcOfferOptions\":[],\"RtcOutboundRtpStreamStats\":[],\"RtcPeerConnection\":[\"EventTarget\"],\"RtcPeerConnectionIceErrorEvent\":[\"Event\"],\"RtcPeerConnectionIceEvent\":[\"Event\"],\"RtcPeerConnectionIceEventInit\":[],\"RtcPeerConnectionState\":[],\"RtcPriorityType\":[],\"RtcRtcpParameters\":[],\"RtcRtpCapabilities\":[],\"RtcRtpCodecCapability\":[],\"RtcRtpCodecParameters\":[],\"RtcRtpContributingSource\":[],\"RtcRtpEncodingParameters\":[],\"RtcRtpHeaderExtensionCapability\":[],\"RtcRtpHeaderExtensionParameters\":[],\"RtcRtpParameters\":[],\"RtcRtpReceiver\":[],\"RtcRtpScriptTransform\":[],\"RtcRtpScriptTransformer\":[\"EventTarget\"],\"RtcRtpSender\":[],\"RtcRtpSourceEntry\":[],\"RtcRtpSourceEntryType\":[],\"RtcRtpSynchronizationSource\":[],\"RtcRtpTransceiver\":[],\"RtcRtpTransceiverDirection\":[],\"RtcRtpTransceiverInit\":[],\"RtcRtxParameters\":[],\"RtcSdpType\":[],\"RtcSessionDescription\":[],\"RtcSessionDescriptionInit\":[],\"RtcSignalingState\":[],\"RtcStats\":[],\"RtcStatsIceCandidatePairState\":[],\"RtcStatsIceCandidateType\":[],\"RtcStatsReport\":[],\"RtcStatsReportInternal\":[],\"RtcStatsType\":[],\"RtcTrackEvent\":[\"Event\"],\"RtcTrackEventInit\":[],\"RtcTransformEvent\":[\"Event\"],\"RtcTransportStats\":[],\"RtcdtmfSender\":[\"EventTarget\"],\"RtcdtmfToneChangeEvent\":[\"Event\"],\"RtcdtmfToneChangeEventInit\":[],\"RtcrtpContributingSourceStats\":[],\"RtcrtpStreamStats\":[],\"SFrameTransform\":[\"EventTarget\"],\"SFrameTransformErrorEvent\":[\"Event\"],\"SFrameTransformErrorEventInit\":[],\"SFrameTransformErrorEventType\":[],\"SFrameTransformOptions\":[],\"SFrameTransformRole\":[],\"SaveFilePickerOptions\":[],\"Scheduler\":[],\"SchedulerPostTaskOptions\":[],\"Scheduling\":[],\"Screen\":[\"EventTarget\"],\"ScreenColorGamut\":[],\"ScreenLuminance\":[],\"ScreenOrientation\":[\"EventTarget\"],\"ScriptProcessorNode\":[\"AudioNode\",\"EventTarget\"],\"ScrollAreaEvent\":[\"Event\",\"UiEvent\"],\"ScrollBehavior\":[],\"ScrollBoxObject\":[],\"ScrollIntoViewOptions\":[],\"ScrollLogicalPosition\":[],\"ScrollOptions\":[],\"ScrollRestoration\":[],\"ScrollSetting\":[],\"ScrollState\":[],\"ScrollToOptions\":[],\"ScrollViewChangeEventInit\":[],\"SecurityPolicyViolationEvent\":[\"Event\"],\"SecurityPolicyViolationEventDisposition\":[],\"SecurityPolicyViolationEventInit\":[],\"Selection\":[],\"SelectionMode\":[],\"Serial\":[\"EventTarget\"],\"SerialInputSignals\":[],\"SerialOptions\":[],\"SerialOutputSignals\":[],\"SerialPort\":[\"EventTarget\"],\"SerialPortFilter\":[],\"SerialPortInfo\":[],\"SerialPortRequestOptions\":[],\"ServerSocketOptions\":[],\"ServiceWorker\":[\"EventTarget\"],\"ServiceWorkerContainer\":[\"EventTarget\"],\"ServiceWorkerGlobalScope\":[\"EventTarget\",\"WorkerGlobalScope\"],\"ServiceWorkerRegistration\":[\"EventTarget\"],\"ServiceWorkerState\":[],\"ServiceWorkerUpdateViaCache\":[],\"ShadowRoot\":[\"DocumentFragment\",\"EventTarget\",\"Node\"],\"ShadowRootInit\":[],\"ShadowRootMode\":[],\"ShareData\":[],\"SharedWorker\":[\"EventTarget\"],\"SharedWorkerGlobalScope\":[\"EventTarget\",\"WorkerGlobalScope\"],\"SignResponse\":[],\"SocketElement\":[],\"SocketOptions\":[],\"SocketReadyState\":[],\"SocketsDict\":[],\"SourceBuffer\":[\"EventTarget\"],\"SourceBufferAppendMode\":[],\"SourceBufferList\":[\"EventTarget\"],\"SpeechGrammar\":[],\"SpeechGrammarList\":[],\"SpeechRecognition\":[\"EventTarget\"],\"SpeechRecognitionAlternative\":[],\"SpeechRecognitionError\":[\"Event\"],\"SpeechRecognitionErrorCode\":[],\"SpeechRecognitionErrorInit\":[],\"SpeechRecognitionEvent\":[\"Event\"],\"SpeechRecognitionEventInit\":[],\"SpeechRecognitionResult\":[],\"SpeechRecognitionResultList\":[],\"SpeechSynthesis\":[\"EventTarget\"],\"SpeechSynthesisErrorCode\":[],\"SpeechSynthesisErrorEvent\":[\"Event\",\"SpeechSynthesisEvent\"],\"SpeechSynthesisErrorEventInit\":[],\"SpeechSynthesisEvent\":[\"Event\"],\"SpeechSynthesisEventInit\":[],\"SpeechSynthesisUtterance\":[\"EventTarget\"],\"SpeechSynthesisVoice\":[],\"StereoPannerNode\":[\"AudioNode\",\"EventTarget\"],\"StereoPannerOptions\":[],\"Storage\":[],\"StorageEstimate\":[],\"StorageEvent\":[\"Event\"],\"StorageEventInit\":[],\"StorageManager\":[],\"StorageType\":[],\"StreamPipeOptions\":[],\"StyleRuleChangeEventInit\":[],\"StyleSheet\":[],\"StyleSheetApplicableStateChangeEventInit\":[],\"StyleSheetChangeEventInit\":[],\"StyleSheetList\":[],\"SubmitEvent\":[\"Event\"],\"SubmitEventInit\":[],\"SubtleCrypto\":[],\"SupportedType\":[],\"SvcOutputMetadata\":[],\"SvgAngle\":[],\"SvgAnimateElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgAnimateMotionElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgAnimateTransformElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgAnimatedAngle\":[],\"SvgAnimatedBoolean\":[],\"SvgAnimatedEnumeration\":[],\"SvgAnimatedInteger\":[],\"SvgAnimatedLength\":[],\"SvgAnimatedLengthList\":[],\"SvgAnimatedNumber\":[],\"SvgAnimatedNumberList\":[],\"SvgAnimatedPreserveAspectRatio\":[],\"SvgAnimatedRect\":[],\"SvgAnimatedString\":[],\"SvgAnimatedTransformList\":[],\"SvgAnimationElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgBoundingBoxOptions\":[],\"SvgCircleElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgClipPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgComponentTransferFunctionElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgDefsElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgDescElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgElement\":[\"Element\",\"EventTarget\",\"Node\"],\"SvgEllipseElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgFilterElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgForeignObjectElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgGeometryElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgGradientElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgGraphicsElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgImageElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgLength\":[],\"SvgLengthList\":[],\"SvgLineElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgLinearGradientElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGradientElement\"],\"SvgMarkerElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgMaskElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgMatrix\":[],\"SvgMetadataElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgNumber\":[],\"SvgNumberList\":[],\"SvgPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgPathSeg\":[],\"SvgPathSegArcAbs\":[\"SvgPathSeg\"],\"SvgPathSegArcRel\":[\"SvgPathSeg\"],\"SvgPathSegClosePath\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicRel\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicSmoothAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicSmoothRel\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticRel\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticSmoothAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticSmoothRel\":[\"SvgPathSeg\"],\"SvgPathSegLinetoAbs\":[\"SvgPathSeg\"],\"SvgPathSegLinetoHorizontalAbs\":[\"SvgPathSeg\"],\"SvgPathSegLinetoHorizontalRel\":[\"SvgPathSeg\"],\"SvgPathSegLinetoRel\":[\"SvgPathSeg\"],\"SvgPathSegLinetoVerticalAbs\":[\"SvgPathSeg\"],\"SvgPathSegLinetoVerticalRel\":[\"SvgPathSeg\"],\"SvgPathSegList\":[],\"SvgPathSegMovetoAbs\":[\"SvgPathSeg\"],\"SvgPathSegMovetoRel\":[\"SvgPathSeg\"],\"SvgPatternElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgPoint\":[],\"SvgPointList\":[],\"SvgPolygonElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgPolylineElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgPreserveAspectRatio\":[],\"SvgRadialGradientElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGradientElement\"],\"SvgRect\":[],\"SvgRectElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgScriptElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgSetElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgStopElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgStringList\":[],\"SvgStyleElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgSwitchElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgSymbolElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgTextContentElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgTextElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\",\"SvgTextPositioningElement\"],\"SvgTextPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\"],\"SvgTextPositioningElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\"],\"SvgTitleElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgTransform\":[],\"SvgTransformList\":[],\"SvgUnitTypes\":[],\"SvgUseElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgViewElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgZoomAndPan\":[],\"SvgaElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgfeBlendElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeColorMatrixElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeComponentTransferElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeCompositeElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeConvolveMatrixElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDiffuseLightingElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDisplacementMapElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDistantLightElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDropShadowElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeFloodElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeFuncAElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeFuncBElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeFuncGElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeFuncRElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeGaussianBlurElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeImageElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeMergeElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeMergeNodeElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeMorphologyElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeOffsetElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfePointLightElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeSpecularLightingElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeSpotLightElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeTileElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeTurbulenceElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvggElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgmPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgsvgElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgtSpanElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\",\"SvgTextPositioningElement\"],\"TaskController\":[\"AbortController\"],\"TaskControllerInit\":[],\"TaskPriority\":[],\"TaskPriorityChangeEvent\":[\"Event\"],\"TaskPriorityChangeEventInit\":[],\"TaskSignal\":[\"AbortSignal\",\"EventTarget\"],\"TaskSignalAnyInit\":[],\"TcpReadyState\":[],\"TcpServerSocket\":[\"EventTarget\"],\"TcpServerSocketEvent\":[\"Event\"],\"TcpServerSocketEventInit\":[],\"TcpSocket\":[\"EventTarget\"],\"TcpSocketBinaryType\":[],\"TcpSocketErrorEvent\":[\"Event\"],\"TcpSocketErrorEventInit\":[],\"TcpSocketEvent\":[\"Event\"],\"TcpSocketEventInit\":[],\"Text\":[\"CharacterData\",\"EventTarget\",\"Node\"],\"TextDecodeOptions\":[],\"TextDecoder\":[],\"TextDecoderOptions\":[],\"TextEncoder\":[],\"TextMetrics\":[],\"TextTrack\":[\"EventTarget\"],\"TextTrackCue\":[\"EventTarget\"],\"TextTrackCueList\":[],\"TextTrackKind\":[],\"TextTrackList\":[\"EventTarget\"],\"TextTrackMode\":[],\"TimeEvent\":[\"Event\"],\"TimeRanges\":[],\"ToggleEvent\":[\"Event\"],\"ToggleEventInit\":[],\"TokenBinding\":[],\"TokenBindingStatus\":[],\"Touch\":[],\"TouchEvent\":[\"Event\",\"UiEvent\"],\"TouchEventInit\":[],\"TouchInit\":[],\"TouchList\":[],\"TrackEvent\":[\"Event\"],\"TrackEventInit\":[],\"TransformStream\":[],\"TransformStreamDefaultController\":[],\"Transformer\":[],\"TransitionEvent\":[\"Event\"],\"TransitionEventInit\":[],\"Transport\":[],\"TreeBoxObject\":[],\"TreeCellInfo\":[],\"TreeView\":[],\"TreeWalker\":[],\"U2f\":[],\"U2fClientData\":[],\"ULongRange\":[],\"UaDataValues\":[],\"UaLowEntropyJson\":[],\"UdpMessageEventInit\":[],\"UdpOptions\":[],\"UiEvent\":[\"Event\"],\"UiEventInit\":[],\"UnderlyingSink\":[],\"UnderlyingSource\":[],\"Url\":[],\"UrlSearchParams\":[],\"Usb\":[\"EventTarget\"],\"UsbAlternateInterface\":[],\"UsbConfiguration\":[],\"UsbConnectionEvent\":[\"Event\"],\"UsbConnectionEventInit\":[],\"UsbControlTransferParameters\":[],\"UsbDevice\":[],\"UsbDeviceFilter\":[],\"UsbDeviceRequestOptions\":[],\"UsbDirection\":[],\"UsbEndpoint\":[],\"UsbEndpointType\":[],\"UsbInTransferResult\":[],\"UsbInterface\":[],\"UsbIsochronousInTransferPacket\":[],\"UsbIsochronousInTransferResult\":[],\"UsbIsochronousOutTransferPacket\":[],\"UsbIsochronousOutTransferResult\":[],\"UsbOutTransferResult\":[],\"UsbPermissionDescriptor\":[],\"UsbPermissionResult\":[\"EventTarget\",\"PermissionStatus\"],\"UsbPermissionStorage\":[],\"UsbRecipient\":[],\"UsbRequestType\":[],\"UsbTransferStatus\":[],\"UserActivation\":[],\"UserProximityEvent\":[\"Event\"],\"UserProximityEventInit\":[],\"UserVerificationRequirement\":[],\"ValidityState\":[],\"ValueEvent\":[\"Event\"],\"ValueEventInit\":[],\"VideoColorPrimaries\":[],\"VideoColorSpace\":[],\"VideoColorSpaceInit\":[],\"VideoConfiguration\":[],\"VideoDecoder\":[],\"VideoDecoderConfig\":[],\"VideoDecoderInit\":[],\"VideoDecoderSupport\":[],\"VideoEncoder\":[],\"VideoEncoderConfig\":[],\"VideoEncoderEncodeOptions\":[],\"VideoEncoderInit\":[],\"VideoEncoderSupport\":[],\"VideoFacingModeEnum\":[],\"VideoFrame\":[],\"VideoFrameBufferInit\":[],\"VideoFrameCopyToOptions\":[],\"VideoFrameInit\":[],\"VideoMatrixCoefficients\":[],\"VideoPixelFormat\":[],\"VideoPlaybackQuality\":[],\"VideoStreamTrack\":[\"EventTarget\",\"MediaStreamTrack\"],\"VideoTrack\":[],\"VideoTrackList\":[\"EventTarget\"],\"VideoTransferCharacteristics\":[],\"ViewTransition\":[],\"VisibilityState\":[],\"VisualViewport\":[\"EventTarget\"],\"VoidCallback\":[],\"VrDisplay\":[\"EventTarget\"],\"VrDisplayCapabilities\":[],\"VrEye\":[],\"VrEyeParameters\":[],\"VrFieldOfView\":[],\"VrFrameData\":[],\"VrLayer\":[],\"VrMockController\":[],\"VrMockDisplay\":[],\"VrPose\":[],\"VrServiceTest\":[],\"VrStageParameters\":[],\"VrSubmitFrameResult\":[],\"VttCue\":[\"EventTarget\",\"TextTrackCue\"],\"VttRegion\":[],\"WakeLock\":[],\"WakeLockSentinel\":[\"EventTarget\"],\"WakeLockType\":[],\"WatchAdvertisementsOptions\":[],\"WaveShaperNode\":[\"AudioNode\",\"EventTarget\"],\"WaveShaperOptions\":[],\"WebGl2RenderingContext\":[],\"WebGlActiveInfo\":[],\"WebGlBuffer\":[],\"WebGlContextAttributes\":[],\"WebGlContextEvent\":[\"Event\"],\"WebGlContextEventInit\":[],\"WebGlFramebuffer\":[],\"WebGlPowerPreference\":[],\"WebGlProgram\":[],\"WebGlQuery\":[],\"WebGlRenderbuffer\":[],\"WebGlRenderingContext\":[],\"WebGlSampler\":[],\"WebGlShader\":[],\"WebGlShaderPrecisionFormat\":[],\"WebGlSync\":[],\"WebGlTexture\":[],\"WebGlTransformFeedback\":[],\"WebGlUniformLocation\":[],\"WebGlVertexArrayObject\":[],\"WebKitCssMatrix\":[\"DomMatrix\",\"DomMatrixReadOnly\"],\"WebSocket\":[\"EventTarget\"],\"WebSocketDict\":[],\"WebSocketElement\":[],\"WebTransport\":[],\"WebTransportBidirectionalStream\":[],\"WebTransportCloseInfo\":[],\"WebTransportCongestionControl\":[],\"WebTransportDatagramDuplexStream\":[],\"WebTransportDatagramStats\":[],\"WebTransportError\":[\"DomException\"],\"WebTransportErrorOptions\":[],\"WebTransportErrorSource\":[],\"WebTransportHash\":[],\"WebTransportOptions\":[],\"WebTransportReceiveStream\":[\"ReadableStream\"],\"WebTransportReceiveStreamStats\":[],\"WebTransportReliabilityMode\":[],\"WebTransportSendStream\":[\"WritableStream\"],\"WebTransportSendStreamOptions\":[],\"WebTransportSendStreamStats\":[],\"WebTransportStats\":[],\"WebglColorBufferFloat\":[],\"WebglCompressedTextureAstc\":[],\"WebglCompressedTextureAtc\":[],\"WebglCompressedTextureEtc\":[],\"WebglCompressedTextureEtc1\":[],\"WebglCompressedTexturePvrtc\":[],\"WebglCompressedTextureS3tc\":[],\"WebglCompressedTextureS3tcSrgb\":[],\"WebglDebugRendererInfo\":[],\"WebglDebugShaders\":[],\"WebglDepthTexture\":[],\"WebglDrawBuffers\":[],\"WebglLoseContext\":[],\"WebglMultiDraw\":[],\"WellKnownDirectory\":[],\"WgslLanguageFeatures\":[],\"WheelEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"WheelEventInit\":[],\"WidevineCdmManifest\":[],\"Window\":[\"EventTarget\"],\"WindowClient\":[\"Client\"],\"Worker\":[\"EventTarget\"],\"WorkerDebuggerGlobalScope\":[\"EventTarget\"],\"WorkerGlobalScope\":[\"EventTarget\"],\"WorkerLocation\":[],\"WorkerNavigator\":[],\"WorkerOptions\":[],\"WorkerType\":[],\"Worklet\":[],\"WorkletGlobalScope\":[],\"WorkletOptions\":[],\"WritableStream\":[],\"WritableStreamDefaultController\":[],\"WritableStreamDefaultWriter\":[],\"WriteCommandType\":[],\"WriteParams\":[],\"XPathExpression\":[],\"XPathNsResolver\":[],\"XPathResult\":[],\"XmlDocument\":[\"Document\",\"EventTarget\",\"Node\"],\"XmlHttpRequest\":[\"EventTarget\",\"XmlHttpRequestEventTarget\"],\"XmlHttpRequestEventTarget\":[\"EventTarget\"],\"XmlHttpRequestResponseType\":[],\"XmlHttpRequestUpload\":[\"EventTarget\",\"XmlHttpRequestEventTarget\"],\"XmlSerializer\":[],\"XrBoundedReferenceSpace\":[\"EventTarget\",\"XrReferenceSpace\",\"XrSpace\"],\"XrEye\":[],\"XrFrame\":[],\"XrHand\":[],\"XrHandJoint\":[],\"XrHandedness\":[],\"XrInputSource\":[],\"XrInputSourceArray\":[],\"XrInputSourceEvent\":[\"Event\"],\"XrInputSourceEventInit\":[],\"XrInputSourcesChangeEvent\":[\"Event\"],\"XrInputSourcesChangeEventInit\":[],\"XrJointPose\":[\"XrPose\"],\"XrJointSpace\":[\"EventTarget\",\"XrSpace\"],\"XrLayer\":[\"EventTarget\"],\"XrPermissionDescriptor\":[],\"XrPermissionStatus\":[\"EventTarget\",\"PermissionStatus\"],\"XrPose\":[],\"XrReferenceSpace\":[\"EventTarget\",\"XrSpace\"],\"XrReferenceSpaceEvent\":[\"Event\"],\"XrReferenceSpaceEventInit\":[],\"XrReferenceSpaceType\":[],\"XrRenderState\":[],\"XrRenderStateInit\":[],\"XrRigidTransform\":[],\"XrSession\":[\"EventTarget\"],\"XrSessionEvent\":[\"Event\"],\"XrSessionEventInit\":[],\"XrSessionInit\":[],\"XrSessionMode\":[],\"XrSessionSupportedPermissionDescriptor\":[],\"XrSpace\":[\"EventTarget\"],\"XrSystem\":[\"EventTarget\"],\"XrTargetRayMode\":[],\"XrView\":[],\"XrViewerPose\":[\"XrPose\"],\"XrViewport\":[],\"XrVisibilityState\":[],\"XrWebGlLayer\":[\"EventTarget\",\"XrLayer\"],\"XrWebGlLayerInit\":[],\"XsltProcessor\":[],\"console\":[],\"css\":[],\"default\":[\"std\"],\"gpu_buffer_usage\":[],\"gpu_color_write\":[],\"gpu_map_mode\":[],\"gpu_shader_stage\":[],\"gpu_texture_usage\":[],\"std\":[\"wasm-bindgen/std\",\"js-sys/std\"]}}", + "wayland-backend_0.3.12": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"concat-idents\",\"req\":\"^1.1\"},{\"name\":\"downcast-rs\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"raw-window-handle\",\"optional\":true,\"req\":\"^0.5.0\"},{\"features\":[\"event\",\"fs\",\"net\",\"process\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"name\":\"rwh_06\",\"optional\":true,\"package\":\"raw-window-handle\",\"req\":\"^0.6.0\"},{\"name\":\"scoped-tls\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"scoped-tls\",\"req\":\"^1.0\"},{\"features\":[\"union\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.9\"},{\"name\":\"wayland-sys\",\"req\":\"^0.31.8\"}],\"features\":{\"client_system\":[\"wayland-sys/client\",\"dep:scoped-tls\"],\"dlopen\":[\"wayland-sys/dlopen\"],\"server_system\":[\"wayland-sys/server\",\"dep:scoped-tls\"]}}", + "wayland-client_0.31.12": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3.16\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"event\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.12\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.8\"}],\"features\":{}}", + "wayland-protocols-wlr_0.3.10": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.12\"},{\"name\":\"wayland-client\",\"optional\":true,\"req\":\"^0.31.12\"},{\"name\":\"wayland-protocols\",\"req\":\"^0.32.10\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.8\"},{\"name\":\"wayland-server\",\"optional\":true,\"req\":\"^0.31.11\"}],\"features\":{\"client\":[\"wayland-client\",\"wayland-protocols/client\"],\"server\":[\"wayland-server\",\"wayland-protocols/server\"]}}", + "wayland-protocols_0.32.10": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.12\"},{\"name\":\"wayland-client\",\"optional\":true,\"req\":\"^0.31.12\"},{\"name\":\"wayland-scanner\",\"req\":\"^0.31.8\"},{\"name\":\"wayland-server\",\"optional\":true,\"req\":\"^0.31.11\"}],\"features\":{\"client\":[\"wayland-client\"],\"server\":[\"wayland-server\"],\"staging\":[],\"unstable\":[]}}", + "wayland-scanner_0.31.8": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.11\"},{\"name\":\"quick-xml\",\"req\":\"^0.38.3\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"similar\",\"req\":\"^2\"}],\"features\":{}}", + "wayland-sys_0.31.8": "{\"dependencies\":[{\"name\":\"dlib\",\"optional\":true,\"req\":\"^0.5.1\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"memoffset\",\"optional\":true,\"req\":\"^0.9\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.7\"}],\"features\":{\"client\":[\"dep:dlib\",\"dep:log\"],\"cursor\":[\"client\"],\"dlopen\":[\"once_cell\"],\"egl\":[\"client\"],\"server\":[\"libc\",\"memoffset\",\"dep:dlib\",\"dep:log\"]}}", + "web-sys_0.3.85": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"js-sys\",\"req\":\"=0.3.85\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.108\"}],\"features\":{\"AbortController\":[],\"AbortSignal\":[\"EventTarget\"],\"AddEventListenerOptions\":[],\"AesCbcParams\":[],\"AesCtrParams\":[],\"AesDerivedKeyParams\":[],\"AesGcmParams\":[],\"AesKeyAlgorithm\":[],\"AesKeyGenParams\":[],\"Algorithm\":[],\"AlignSetting\":[],\"AllowedBluetoothDevice\":[],\"AllowedUsbDevice\":[],\"AlphaOption\":[],\"AnalyserNode\":[\"AudioNode\",\"EventTarget\"],\"AnalyserOptions\":[],\"AngleInstancedArrays\":[],\"Animation\":[\"EventTarget\"],\"AnimationEffect\":[],\"AnimationEvent\":[\"Event\"],\"AnimationEventInit\":[],\"AnimationPlayState\":[],\"AnimationPlaybackEvent\":[\"Event\"],\"AnimationPlaybackEventInit\":[],\"AnimationPropertyDetails\":[],\"AnimationPropertyValueDetails\":[],\"AnimationTimeline\":[],\"AssignedNodesOptions\":[],\"AttestationConveyancePreference\":[],\"Attr\":[\"EventTarget\",\"Node\"],\"AttributeNameValue\":[],\"AudioBuffer\":[],\"AudioBufferOptions\":[],\"AudioBufferSourceNode\":[\"AudioNode\",\"AudioScheduledSourceNode\",\"EventTarget\"],\"AudioBufferSourceOptions\":[],\"AudioConfiguration\":[],\"AudioContext\":[\"BaseAudioContext\",\"EventTarget\"],\"AudioContextLatencyCategory\":[],\"AudioContextOptions\":[],\"AudioContextState\":[],\"AudioData\":[],\"AudioDataCopyToOptions\":[],\"AudioDataInit\":[],\"AudioDecoder\":[],\"AudioDecoderConfig\":[],\"AudioDecoderInit\":[],\"AudioDecoderSupport\":[],\"AudioDestinationNode\":[\"AudioNode\",\"EventTarget\"],\"AudioEncoder\":[],\"AudioEncoderConfig\":[],\"AudioEncoderInit\":[],\"AudioEncoderSupport\":[],\"AudioListener\":[],\"AudioNode\":[\"EventTarget\"],\"AudioNodeOptions\":[],\"AudioParam\":[],\"AudioParamMap\":[],\"AudioProcessingEvent\":[\"Event\"],\"AudioSampleFormat\":[],\"AudioScheduledSourceNode\":[\"AudioNode\",\"EventTarget\"],\"AudioSinkInfo\":[],\"AudioSinkOptions\":[],\"AudioSinkType\":[],\"AudioStreamTrack\":[\"EventTarget\",\"MediaStreamTrack\"],\"AudioTrack\":[],\"AudioTrackList\":[\"EventTarget\"],\"AudioWorklet\":[\"Worklet\"],\"AudioWorkletGlobalScope\":[\"WorkletGlobalScope\"],\"AudioWorkletNode\":[\"AudioNode\",\"EventTarget\"],\"AudioWorkletNodeOptions\":[],\"AudioWorkletProcessor\":[],\"AuthenticationExtensionsClientInputs\":[],\"AuthenticationExtensionsClientInputsJson\":[],\"AuthenticationExtensionsClientOutputs\":[],\"AuthenticationExtensionsClientOutputsJson\":[],\"AuthenticationExtensionsDevicePublicKeyInputs\":[],\"AuthenticationExtensionsDevicePublicKeyOutputs\":[],\"AuthenticationExtensionsLargeBlobInputs\":[],\"AuthenticationExtensionsLargeBlobOutputs\":[],\"AuthenticationExtensionsPrfInputs\":[],\"AuthenticationExtensionsPrfOutputs\":[],\"AuthenticationExtensionsPrfValues\":[],\"AuthenticationResponseJson\":[],\"AuthenticatorAssertionResponse\":[\"AuthenticatorResponse\"],\"AuthenticatorAssertionResponseJson\":[],\"AuthenticatorAttachment\":[],\"AuthenticatorAttestationResponse\":[\"AuthenticatorResponse\"],\"AuthenticatorAttestationResponseJson\":[],\"AuthenticatorResponse\":[],\"AuthenticatorSelectionCriteria\":[],\"AuthenticatorTransport\":[],\"AutoKeyword\":[],\"AutocompleteInfo\":[],\"BarProp\":[],\"BaseAudioContext\":[\"EventTarget\"],\"BaseComputedKeyframe\":[],\"BaseKeyframe\":[],\"BasePropertyIndexedKeyframe\":[],\"BasicCardRequest\":[],\"BasicCardResponse\":[],\"BasicCardType\":[],\"BatteryManager\":[\"EventTarget\"],\"BeforeUnloadEvent\":[\"Event\"],\"BinaryType\":[],\"BiquadFilterNode\":[\"AudioNode\",\"EventTarget\"],\"BiquadFilterOptions\":[],\"BiquadFilterType\":[],\"Blob\":[],\"BlobEvent\":[\"Event\"],\"BlobEventInit\":[],\"BlobPropertyBag\":[],\"BlockParsingOptions\":[],\"Bluetooth\":[\"EventTarget\"],\"BluetoothAdvertisingEvent\":[\"Event\"],\"BluetoothAdvertisingEventInit\":[],\"BluetoothCharacteristicProperties\":[],\"BluetoothDataFilterInit\":[],\"BluetoothDevice\":[\"EventTarget\"],\"BluetoothLeScanFilterInit\":[],\"BluetoothManufacturerDataMap\":[],\"BluetoothPermissionDescriptor\":[],\"BluetoothPermissionResult\":[\"EventTarget\",\"PermissionStatus\"],\"BluetoothPermissionStorage\":[],\"BluetoothRemoteGattCharacteristic\":[\"EventTarget\"],\"BluetoothRemoteGattDescriptor\":[],\"BluetoothRemoteGattServer\":[],\"BluetoothRemoteGattService\":[\"EventTarget\"],\"BluetoothServiceDataMap\":[],\"BluetoothUuid\":[],\"BoxQuadOptions\":[],\"BroadcastChannel\":[\"EventTarget\"],\"BrowserElementDownloadOptions\":[],\"BrowserElementExecuteScriptOptions\":[],\"BrowserFeedWriter\":[],\"BrowserFindCaseSensitivity\":[],\"BrowserFindDirection\":[],\"ByteLengthQueuingStrategy\":[],\"Cache\":[],\"CacheBatchOperation\":[],\"CacheQueryOptions\":[],\"CacheStorage\":[],\"CacheStorageNamespace\":[],\"CanvasCaptureMediaStream\":[\"EventTarget\",\"MediaStream\"],\"CanvasCaptureMediaStreamTrack\":[\"EventTarget\",\"MediaStreamTrack\"],\"CanvasGradient\":[],\"CanvasPattern\":[],\"CanvasRenderingContext2d\":[],\"CanvasWindingRule\":[],\"CaretChangedReason\":[],\"CaretPosition\":[],\"CaretStateChangedEventInit\":[],\"CdataSection\":[\"CharacterData\",\"EventTarget\",\"Node\",\"Text\"],\"ChannelCountMode\":[],\"ChannelInterpretation\":[],\"ChannelMergerNode\":[\"AudioNode\",\"EventTarget\"],\"ChannelMergerOptions\":[],\"ChannelSplitterNode\":[\"AudioNode\",\"EventTarget\"],\"ChannelSplitterOptions\":[],\"CharacterData\":[\"EventTarget\",\"Node\"],\"CheckerboardReason\":[],\"CheckerboardReport\":[],\"CheckerboardReportService\":[],\"ChromeFilePropertyBag\":[],\"ChromeWorker\":[\"EventTarget\",\"Worker\"],\"Client\":[],\"ClientQueryOptions\":[],\"ClientRectsAndTexts\":[],\"ClientType\":[],\"Clients\":[],\"Clipboard\":[\"EventTarget\"],\"ClipboardEvent\":[\"Event\"],\"ClipboardEventInit\":[],\"ClipboardItem\":[],\"ClipboardItemOptions\":[],\"ClipboardPermissionDescriptor\":[],\"ClipboardUnsanitizedFormats\":[],\"CloseEvent\":[\"Event\"],\"CloseEventInit\":[],\"CodecState\":[],\"CollectedClientData\":[],\"ColorSpaceConversion\":[],\"Comment\":[\"CharacterData\",\"EventTarget\",\"Node\"],\"CompositeOperation\":[],\"CompositionEvent\":[\"Event\",\"UiEvent\"],\"CompositionEventInit\":[],\"CompressionFormat\":[],\"CompressionStream\":[],\"ComputedEffectTiming\":[],\"ConnStatusDict\":[],\"ConnectionType\":[],\"ConsoleCounter\":[],\"ConsoleCounterError\":[],\"ConsoleEvent\":[],\"ConsoleInstance\":[],\"ConsoleInstanceOptions\":[],\"ConsoleLevel\":[],\"ConsoleLogLevel\":[],\"ConsoleProfileEvent\":[],\"ConsoleStackEntry\":[],\"ConsoleTimerError\":[],\"ConsoleTimerLogOrEnd\":[],\"ConsoleTimerStart\":[],\"ConstantSourceNode\":[\"AudioNode\",\"AudioScheduledSourceNode\",\"EventTarget\"],\"ConstantSourceOptions\":[],\"ConstrainBooleanParameters\":[],\"ConstrainDomStringParameters\":[],\"ConstrainDoubleRange\":[],\"ConstrainLongRange\":[],\"ContextAttributes2d\":[],\"ConvertCoordinateOptions\":[],\"ConvolverNode\":[\"AudioNode\",\"EventTarget\"],\"ConvolverOptions\":[],\"CookieChangeEvent\":[\"Event\"],\"CookieChangeEventInit\":[],\"CookieInit\":[],\"CookieListItem\":[],\"CookieSameSite\":[],\"CookieStore\":[\"EventTarget\"],\"CookieStoreDeleteOptions\":[],\"CookieStoreGetOptions\":[],\"CookieStoreManager\":[],\"Coordinates\":[],\"CountQueuingStrategy\":[],\"Credential\":[],\"CredentialCreationOptions\":[],\"CredentialPropertiesOutput\":[],\"CredentialRequestOptions\":[],\"CredentialsContainer\":[],\"Crypto\":[],\"CryptoKey\":[],\"CryptoKeyPair\":[],\"CssAnimation\":[\"Animation\",\"EventTarget\"],\"CssBoxType\":[],\"CssConditionRule\":[\"CssGroupingRule\",\"CssRule\"],\"CssCounterStyleRule\":[\"CssRule\"],\"CssFontFaceRule\":[\"CssRule\"],\"CssFontFeatureValuesRule\":[\"CssRule\"],\"CssGroupingRule\":[\"CssRule\"],\"CssImportRule\":[\"CssRule\"],\"CssKeyframeRule\":[\"CssRule\"],\"CssKeyframesRule\":[\"CssRule\"],\"CssMediaRule\":[\"CssConditionRule\",\"CssGroupingRule\",\"CssRule\"],\"CssNamespaceRule\":[\"CssRule\"],\"CssPageRule\":[\"CssRule\"],\"CssPseudoElement\":[],\"CssRule\":[],\"CssRuleList\":[],\"CssStyleDeclaration\":[],\"CssStyleRule\":[\"CssRule\"],\"CssStyleSheet\":[\"StyleSheet\"],\"CssStyleSheetParsingMode\":[],\"CssSupportsRule\":[\"CssConditionRule\",\"CssGroupingRule\",\"CssRule\"],\"CssTransition\":[\"Animation\",\"EventTarget\"],\"CustomElementRegistry\":[],\"CustomEvent\":[\"Event\"],\"CustomEventInit\":[],\"DataTransfer\":[],\"DataTransferItem\":[],\"DataTransferItemList\":[],\"DateTimeValue\":[],\"DecoderDoctorNotification\":[],\"DecoderDoctorNotificationType\":[],\"DecompressionStream\":[],\"DedicatedWorkerGlobalScope\":[\"EventTarget\",\"WorkerGlobalScope\"],\"DelayNode\":[\"AudioNode\",\"EventTarget\"],\"DelayOptions\":[],\"DeviceAcceleration\":[],\"DeviceAccelerationInit\":[],\"DeviceLightEvent\":[\"Event\"],\"DeviceLightEventInit\":[],\"DeviceMotionEvent\":[\"Event\"],\"DeviceMotionEventInit\":[],\"DeviceOrientationEvent\":[\"Event\"],\"DeviceOrientationEventInit\":[],\"DeviceProximityEvent\":[\"Event\"],\"DeviceProximityEventInit\":[],\"DeviceRotationRate\":[],\"DeviceRotationRateInit\":[],\"DhKeyDeriveParams\":[],\"DirectionSetting\":[],\"Directory\":[],\"DirectoryPickerOptions\":[],\"DisplayMediaStreamConstraints\":[],\"DisplayNameOptions\":[],\"DisplayNameResult\":[],\"DistanceModelType\":[],\"DnsCacheDict\":[],\"DnsCacheEntry\":[],\"DnsLookupDict\":[],\"Document\":[\"EventTarget\",\"Node\"],\"DocumentFragment\":[\"EventTarget\",\"Node\"],\"DocumentTimeline\":[\"AnimationTimeline\"],\"DocumentTimelineOptions\":[],\"DocumentType\":[\"EventTarget\",\"Node\"],\"DomError\":[],\"DomException\":[],\"DomImplementation\":[],\"DomMatrix\":[\"DomMatrixReadOnly\"],\"DomMatrix2dInit\":[],\"DomMatrixInit\":[],\"DomMatrixReadOnly\":[],\"DomParser\":[],\"DomPoint\":[\"DomPointReadOnly\"],\"DomPointInit\":[],\"DomPointReadOnly\":[],\"DomQuad\":[],\"DomQuadInit\":[],\"DomQuadJson\":[],\"DomRect\":[\"DomRectReadOnly\"],\"DomRectInit\":[],\"DomRectList\":[],\"DomRectReadOnly\":[],\"DomRequest\":[\"EventTarget\"],\"DomRequestReadyState\":[],\"DomStringList\":[],\"DomStringMap\":[],\"DomTokenList\":[],\"DomWindowResizeEventDetail\":[],\"DoubleRange\":[],\"DragEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"DragEventInit\":[],\"DynamicsCompressorNode\":[\"AudioNode\",\"EventTarget\"],\"DynamicsCompressorOptions\":[],\"EcKeyAlgorithm\":[],\"EcKeyGenParams\":[],\"EcKeyImportParams\":[],\"EcdhKeyDeriveParams\":[],\"EcdsaParams\":[],\"EffectTiming\":[],\"Element\":[\"EventTarget\",\"Node\"],\"ElementCreationOptions\":[],\"ElementDefinitionOptions\":[],\"EncodedAudioChunk\":[],\"EncodedAudioChunkInit\":[],\"EncodedAudioChunkMetadata\":[],\"EncodedAudioChunkType\":[],\"EncodedVideoChunk\":[],\"EncodedVideoChunkInit\":[],\"EncodedVideoChunkMetadata\":[],\"EncodedVideoChunkType\":[],\"EndingTypes\":[],\"ErrorCallback\":[],\"ErrorEvent\":[\"Event\"],\"ErrorEventInit\":[],\"Event\":[],\"EventInit\":[],\"EventListener\":[],\"EventListenerOptions\":[],\"EventModifierInit\":[],\"EventSource\":[\"EventTarget\"],\"EventSourceInit\":[],\"EventTarget\":[],\"Exception\":[],\"ExtBlendMinmax\":[],\"ExtColorBufferFloat\":[],\"ExtColorBufferHalfFloat\":[],\"ExtDisjointTimerQuery\":[],\"ExtFragDepth\":[],\"ExtSRgb\":[],\"ExtShaderTextureLod\":[],\"ExtTextureFilterAnisotropic\":[],\"ExtTextureNorm16\":[],\"ExtendableCookieChangeEvent\":[\"Event\",\"ExtendableEvent\"],\"ExtendableCookieChangeEventInit\":[],\"ExtendableEvent\":[\"Event\"],\"ExtendableEventInit\":[],\"ExtendableMessageEvent\":[\"Event\",\"ExtendableEvent\"],\"ExtendableMessageEventInit\":[],\"External\":[],\"FakePluginMimeEntry\":[],\"FakePluginTagInit\":[],\"FetchEvent\":[\"Event\",\"ExtendableEvent\"],\"FetchEventInit\":[],\"FetchObserver\":[\"EventTarget\"],\"FetchReadableStreamReadDataArray\":[],\"FetchReadableStreamReadDataDone\":[],\"FetchState\":[],\"File\":[\"Blob\"],\"FileCallback\":[],\"FileList\":[],\"FilePickerAcceptType\":[],\"FilePickerOptions\":[],\"FilePropertyBag\":[],\"FileReader\":[\"EventTarget\"],\"FileReaderSync\":[],\"FileSystem\":[],\"FileSystemCreateWritableOptions\":[],\"FileSystemDirectoryEntry\":[\"FileSystemEntry\"],\"FileSystemDirectoryHandle\":[\"FileSystemHandle\"],\"FileSystemDirectoryReader\":[],\"FileSystemEntriesCallback\":[],\"FileSystemEntry\":[],\"FileSystemEntryCallback\":[],\"FileSystemFileEntry\":[\"FileSystemEntry\"],\"FileSystemFileHandle\":[\"FileSystemHandle\"],\"FileSystemFlags\":[],\"FileSystemGetDirectoryOptions\":[],\"FileSystemGetFileOptions\":[],\"FileSystemHandle\":[],\"FileSystemHandleKind\":[],\"FileSystemHandlePermissionDescriptor\":[],\"FileSystemPermissionDescriptor\":[],\"FileSystemPermissionMode\":[],\"FileSystemReadWriteOptions\":[],\"FileSystemRemoveOptions\":[],\"FileSystemSyncAccessHandle\":[],\"FileSystemWritableFileStream\":[\"WritableStream\"],\"FillMode\":[],\"FlashClassification\":[],\"FlowControlType\":[],\"FocusEvent\":[\"Event\",\"UiEvent\"],\"FocusEventInit\":[],\"FocusOptions\":[],\"FontData\":[],\"FontFace\":[],\"FontFaceDescriptors\":[],\"FontFaceLoadStatus\":[],\"FontFaceSet\":[\"EventTarget\"],\"FontFaceSetIterator\":[],\"FontFaceSetIteratorResult\":[],\"FontFaceSetLoadEvent\":[\"Event\"],\"FontFaceSetLoadEventInit\":[],\"FontFaceSetLoadStatus\":[],\"FormData\":[],\"FrameType\":[],\"FuzzingFunctions\":[],\"GainNode\":[\"AudioNode\",\"EventTarget\"],\"GainOptions\":[],\"Gamepad\":[],\"GamepadButton\":[],\"GamepadEffectParameters\":[],\"GamepadEvent\":[\"Event\"],\"GamepadEventInit\":[],\"GamepadHand\":[],\"GamepadHapticActuator\":[],\"GamepadHapticActuatorType\":[],\"GamepadHapticEffectType\":[],\"GamepadHapticsResult\":[],\"GamepadMappingType\":[],\"GamepadPose\":[],\"GamepadTouch\":[],\"Geolocation\":[],\"GestureEvent\":[\"Event\",\"UiEvent\"],\"GetAnimationsOptions\":[],\"GetRootNodeOptions\":[],\"GetUserMediaRequest\":[],\"Gpu\":[],\"GpuAdapter\":[],\"GpuAdapterInfo\":[],\"GpuAddressMode\":[],\"GpuAutoLayoutMode\":[],\"GpuBindGroup\":[],\"GpuBindGroupDescriptor\":[],\"GpuBindGroupEntry\":[],\"GpuBindGroupLayout\":[],\"GpuBindGroupLayoutDescriptor\":[],\"GpuBindGroupLayoutEntry\":[],\"GpuBlendComponent\":[],\"GpuBlendFactor\":[],\"GpuBlendOperation\":[],\"GpuBlendState\":[],\"GpuBuffer\":[],\"GpuBufferBinding\":[],\"GpuBufferBindingLayout\":[],\"GpuBufferBindingType\":[],\"GpuBufferDescriptor\":[],\"GpuBufferMapState\":[],\"GpuCanvasAlphaMode\":[],\"GpuCanvasConfiguration\":[],\"GpuCanvasContext\":[],\"GpuCanvasToneMapping\":[],\"GpuCanvasToneMappingMode\":[],\"GpuColorDict\":[],\"GpuColorTargetState\":[],\"GpuCommandBuffer\":[],\"GpuCommandBufferDescriptor\":[],\"GpuCommandEncoder\":[],\"GpuCommandEncoderDescriptor\":[],\"GpuCompareFunction\":[],\"GpuCompilationInfo\":[],\"GpuCompilationMessage\":[],\"GpuCompilationMessageType\":[],\"GpuComputePassDescriptor\":[],\"GpuComputePassEncoder\":[],\"GpuComputePassTimestampWrites\":[],\"GpuComputePipeline\":[],\"GpuComputePipelineDescriptor\":[],\"GpuCopyExternalImageDestInfo\":[],\"GpuCopyExternalImageSourceInfo\":[],\"GpuCullMode\":[],\"GpuDepthStencilState\":[],\"GpuDevice\":[\"EventTarget\"],\"GpuDeviceDescriptor\":[],\"GpuDeviceLostInfo\":[],\"GpuDeviceLostReason\":[],\"GpuError\":[],\"GpuErrorFilter\":[],\"GpuExtent3dDict\":[],\"GpuExternalTexture\":[],\"GpuExternalTextureBindingLayout\":[],\"GpuExternalTextureDescriptor\":[],\"GpuFeatureName\":[],\"GpuFilterMode\":[],\"GpuFragmentState\":[],\"GpuFrontFace\":[],\"GpuIndexFormat\":[],\"GpuInternalError\":[\"GpuError\"],\"GpuLoadOp\":[],\"GpuMipmapFilterMode\":[],\"GpuMultisampleState\":[],\"GpuObjectDescriptorBase\":[],\"GpuOrigin2dDict\":[],\"GpuOrigin3dDict\":[],\"GpuOutOfMemoryError\":[\"GpuError\"],\"GpuPipelineDescriptorBase\":[],\"GpuPipelineError\":[\"DomException\"],\"GpuPipelineErrorInit\":[],\"GpuPipelineErrorReason\":[],\"GpuPipelineLayout\":[],\"GpuPipelineLayoutDescriptor\":[],\"GpuPowerPreference\":[],\"GpuPrimitiveState\":[],\"GpuPrimitiveTopology\":[],\"GpuProgrammableStage\":[],\"GpuQuerySet\":[],\"GpuQuerySetDescriptor\":[],\"GpuQueryType\":[],\"GpuQueue\":[],\"GpuQueueDescriptor\":[],\"GpuRenderBundle\":[],\"GpuRenderBundleDescriptor\":[],\"GpuRenderBundleEncoder\":[],\"GpuRenderBundleEncoderDescriptor\":[],\"GpuRenderPassColorAttachment\":[],\"GpuRenderPassDepthStencilAttachment\":[],\"GpuRenderPassDescriptor\":[],\"GpuRenderPassEncoder\":[],\"GpuRenderPassLayout\":[],\"GpuRenderPassTimestampWrites\":[],\"GpuRenderPipeline\":[],\"GpuRenderPipelineDescriptor\":[],\"GpuRequestAdapterOptions\":[],\"GpuSampler\":[],\"GpuSamplerBindingLayout\":[],\"GpuSamplerBindingType\":[],\"GpuSamplerDescriptor\":[],\"GpuShaderModule\":[],\"GpuShaderModuleCompilationHint\":[],\"GpuShaderModuleDescriptor\":[],\"GpuStencilFaceState\":[],\"GpuStencilOperation\":[],\"GpuStorageTextureAccess\":[],\"GpuStorageTextureBindingLayout\":[],\"GpuStoreOp\":[],\"GpuSupportedFeatures\":[],\"GpuSupportedLimits\":[],\"GpuTexelCopyBufferInfo\":[],\"GpuTexelCopyBufferLayout\":[],\"GpuTexelCopyTextureInfo\":[],\"GpuTexture\":[],\"GpuTextureAspect\":[],\"GpuTextureBindingLayout\":[],\"GpuTextureDescriptor\":[],\"GpuTextureDimension\":[],\"GpuTextureFormat\":[],\"GpuTextureSampleType\":[],\"GpuTextureView\":[],\"GpuTextureViewDescriptor\":[],\"GpuTextureViewDimension\":[],\"GpuUncapturedErrorEvent\":[\"Event\"],\"GpuUncapturedErrorEventInit\":[],\"GpuValidationError\":[\"GpuError\"],\"GpuVertexAttribute\":[],\"GpuVertexBufferLayout\":[],\"GpuVertexFormat\":[],\"GpuVertexState\":[],\"GpuVertexStepMode\":[],\"GroupedHistoryEventInit\":[],\"HalfOpenInfoDict\":[],\"HardwareAcceleration\":[],\"HashChangeEvent\":[\"Event\"],\"HashChangeEventInit\":[],\"Headers\":[],\"HeadersGuardEnum\":[],\"Hid\":[\"EventTarget\"],\"HidCollectionInfo\":[],\"HidConnectionEvent\":[\"Event\"],\"HidConnectionEventInit\":[],\"HidDevice\":[\"EventTarget\"],\"HidDeviceFilter\":[],\"HidDeviceRequestOptions\":[],\"HidInputReportEvent\":[\"Event\"],\"HidInputReportEventInit\":[],\"HidReportInfo\":[],\"HidReportItem\":[],\"HidUnitSystem\":[],\"HiddenPluginEventInit\":[],\"History\":[],\"HitRegionOptions\":[],\"HkdfParams\":[],\"HmacDerivedKeyParams\":[],\"HmacImportParams\":[],\"HmacKeyAlgorithm\":[],\"HmacKeyGenParams\":[],\"HtmlAllCollection\":[],\"HtmlAnchorElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlAreaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlAudioElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"HtmlMediaElement\",\"Node\"],\"HtmlBaseElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlBodyElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlBrElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlButtonElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlCanvasElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlCollection\":[],\"HtmlDListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDataElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDataListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDetailsElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDialogElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDirectoryElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDivElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlDocument\":[\"Document\",\"EventTarget\",\"Node\"],\"HtmlElement\":[\"Element\",\"EventTarget\",\"Node\"],\"HtmlEmbedElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFieldSetElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFontElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFormControlsCollection\":[\"HtmlCollection\"],\"HtmlFormElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFrameElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlFrameSetElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHeadElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHeadingElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHrElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlHtmlElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlIFrameElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlImageElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlInputElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLabelElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLegendElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLiElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlLinkElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMapElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMediaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMenuElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMenuItemElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMetaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlMeterElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlModElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlObjectElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOptGroupElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOptionElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlOptionsCollection\":[\"HtmlCollection\"],\"HtmlOutputElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlParagraphElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlParamElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlPictureElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlPreElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlProgressElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlQuoteElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlScriptElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSelectElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSlotElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSourceElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlSpanElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlStyleElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableCaptionElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableCellElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableColElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableRowElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTableSectionElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTemplateElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTextAreaElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTimeElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTitleElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlTrackElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlUListElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlUnknownElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"Node\"],\"HtmlVideoElement\":[\"Element\",\"EventTarget\",\"HtmlElement\",\"HtmlMediaElement\",\"Node\"],\"HttpConnDict\":[],\"HttpConnInfo\":[],\"HttpConnectionElement\":[],\"IdbCursor\":[],\"IdbCursorDirection\":[],\"IdbCursorWithValue\":[\"IdbCursor\"],\"IdbDatabase\":[\"EventTarget\"],\"IdbFactory\":[],\"IdbFileHandle\":[\"EventTarget\"],\"IdbFileMetadataParameters\":[],\"IdbFileRequest\":[\"DomRequest\",\"EventTarget\"],\"IdbIndex\":[],\"IdbIndexParameters\":[],\"IdbKeyRange\":[],\"IdbLocaleAwareKeyRange\":[\"IdbKeyRange\"],\"IdbMutableFile\":[\"EventTarget\"],\"IdbObjectStore\":[],\"IdbObjectStoreParameters\":[],\"IdbOpenDbOptions\":[],\"IdbOpenDbRequest\":[\"EventTarget\",\"IdbRequest\"],\"IdbRequest\":[\"EventTarget\"],\"IdbRequestReadyState\":[],\"IdbTransaction\":[\"EventTarget\"],\"IdbTransactionDurability\":[],\"IdbTransactionMode\":[],\"IdbTransactionOptions\":[],\"IdbVersionChangeEvent\":[\"Event\"],\"IdbVersionChangeEventInit\":[],\"IdleDeadline\":[],\"IdleRequestOptions\":[],\"IirFilterNode\":[\"AudioNode\",\"EventTarget\"],\"IirFilterOptions\":[],\"ImageBitmap\":[],\"ImageBitmapOptions\":[],\"ImageBitmapRenderingContext\":[],\"ImageCapture\":[],\"ImageCaptureError\":[],\"ImageCaptureErrorEvent\":[\"Event\"],\"ImageCaptureErrorEventInit\":[],\"ImageData\":[],\"ImageDecodeOptions\":[],\"ImageDecodeResult\":[],\"ImageDecoder\":[],\"ImageDecoderInit\":[],\"ImageEncodeOptions\":[],\"ImageOrientation\":[],\"ImageTrack\":[\"EventTarget\"],\"ImageTrackList\":[],\"InputDeviceInfo\":[\"MediaDeviceInfo\"],\"InputEvent\":[\"Event\",\"UiEvent\"],\"InputEventInit\":[],\"IntersectionObserver\":[],\"IntersectionObserverEntry\":[],\"IntersectionObserverEntryInit\":[],\"IntersectionObserverInit\":[],\"IntlUtils\":[],\"IsInputPendingOptions\":[],\"IterableKeyAndValueResult\":[],\"IterableKeyOrValueResult\":[],\"IterationCompositeOperation\":[],\"JsonWebKey\":[],\"KeyAlgorithm\":[],\"KeyEvent\":[],\"KeyFrameRequestEvent\":[\"Event\"],\"KeyIdsInitData\":[],\"KeyboardEvent\":[\"Event\",\"UiEvent\"],\"KeyboardEventInit\":[],\"KeyframeAnimationOptions\":[],\"KeyframeEffect\":[\"AnimationEffect\"],\"KeyframeEffectOptions\":[],\"L10nElement\":[],\"L10nValue\":[],\"LargeBlobSupport\":[],\"LatencyMode\":[],\"LifecycleCallbacks\":[],\"LineAlignSetting\":[],\"ListBoxObject\":[],\"LocalMediaStream\":[\"EventTarget\",\"MediaStream\"],\"LocaleInfo\":[],\"Location\":[],\"Lock\":[],\"LockInfo\":[],\"LockManager\":[],\"LockManagerSnapshot\":[],\"LockMode\":[],\"LockOptions\":[],\"MathMlElement\":[\"Element\",\"EventTarget\",\"Node\"],\"MediaCapabilities\":[],\"MediaCapabilitiesInfo\":[],\"MediaConfiguration\":[],\"MediaDecodingConfiguration\":[],\"MediaDecodingType\":[],\"MediaDeviceInfo\":[],\"MediaDeviceKind\":[],\"MediaDevices\":[\"EventTarget\"],\"MediaElementAudioSourceNode\":[\"AudioNode\",\"EventTarget\"],\"MediaElementAudioSourceOptions\":[],\"MediaEncodingConfiguration\":[],\"MediaEncodingType\":[],\"MediaEncryptedEvent\":[\"Event\"],\"MediaError\":[],\"MediaImage\":[],\"MediaKeyError\":[\"Event\"],\"MediaKeyMessageEvent\":[\"Event\"],\"MediaKeyMessageEventInit\":[],\"MediaKeyMessageType\":[],\"MediaKeyNeededEventInit\":[],\"MediaKeySession\":[\"EventTarget\"],\"MediaKeySessionType\":[],\"MediaKeyStatus\":[],\"MediaKeyStatusMap\":[],\"MediaKeySystemAccess\":[],\"MediaKeySystemConfiguration\":[],\"MediaKeySystemMediaCapability\":[],\"MediaKeySystemStatus\":[],\"MediaKeys\":[],\"MediaKeysPolicy\":[],\"MediaKeysRequirement\":[],\"MediaList\":[],\"MediaMetadata\":[],\"MediaMetadataInit\":[],\"MediaPositionState\":[],\"MediaQueryList\":[\"EventTarget\"],\"MediaQueryListEvent\":[\"Event\"],\"MediaQueryListEventInit\":[],\"MediaRecorder\":[\"EventTarget\"],\"MediaRecorderErrorEvent\":[\"Event\"],\"MediaRecorderErrorEventInit\":[],\"MediaRecorderOptions\":[],\"MediaSession\":[],\"MediaSessionAction\":[],\"MediaSessionActionDetails\":[],\"MediaSessionPlaybackState\":[],\"MediaSource\":[\"EventTarget\"],\"MediaSourceEndOfStreamError\":[],\"MediaSourceEnum\":[],\"MediaSourceReadyState\":[],\"MediaStream\":[\"EventTarget\"],\"MediaStreamAudioDestinationNode\":[\"AudioNode\",\"EventTarget\"],\"MediaStreamAudioSourceNode\":[\"AudioNode\",\"EventTarget\"],\"MediaStreamAudioSourceOptions\":[],\"MediaStreamConstraints\":[],\"MediaStreamError\":[],\"MediaStreamEvent\":[\"Event\"],\"MediaStreamEventInit\":[],\"MediaStreamTrack\":[\"EventTarget\"],\"MediaStreamTrackEvent\":[\"Event\"],\"MediaStreamTrackEventInit\":[],\"MediaStreamTrackGenerator\":[\"EventTarget\",\"MediaStreamTrack\"],\"MediaStreamTrackGeneratorInit\":[],\"MediaStreamTrackProcessor\":[],\"MediaStreamTrackProcessorInit\":[],\"MediaStreamTrackState\":[],\"MediaTrackCapabilities\":[],\"MediaTrackConstraintSet\":[],\"MediaTrackConstraints\":[],\"MediaTrackSettings\":[],\"MediaTrackSupportedConstraints\":[],\"MemoryAttribution\":[],\"MemoryAttributionContainer\":[],\"MemoryBreakdownEntry\":[],\"MemoryMeasurement\":[],\"MessageChannel\":[],\"MessageEvent\":[\"Event\"],\"MessageEventInit\":[],\"MessagePort\":[\"EventTarget\"],\"MidiAccess\":[\"EventTarget\"],\"MidiConnectionEvent\":[\"Event\"],\"MidiConnectionEventInit\":[],\"MidiInput\":[\"EventTarget\",\"MidiPort\"],\"MidiInputMap\":[],\"MidiMessageEvent\":[\"Event\"],\"MidiMessageEventInit\":[],\"MidiOptions\":[],\"MidiOutput\":[\"EventTarget\",\"MidiPort\"],\"MidiOutputMap\":[],\"MidiPort\":[\"EventTarget\"],\"MidiPortConnectionState\":[],\"MidiPortDeviceState\":[],\"MidiPortType\":[],\"MimeType\":[],\"MimeTypeArray\":[],\"MouseEvent\":[\"Event\",\"UiEvent\"],\"MouseEventInit\":[],\"MouseScrollEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"MozDebug\":[],\"MutationEvent\":[\"Event\"],\"MutationObserver\":[],\"MutationObserverInit\":[],\"MutationObservingInfo\":[],\"MutationRecord\":[],\"NamedNodeMap\":[],\"NativeOsFileReadOptions\":[],\"NativeOsFileWriteAtomicOptions\":[],\"NavigationType\":[],\"Navigator\":[],\"NavigatorAutomationInformation\":[],\"NavigatorUaBrandVersion\":[],\"NavigatorUaData\":[],\"NetworkCommandOptions\":[],\"NetworkInformation\":[\"EventTarget\"],\"NetworkResultOptions\":[],\"Node\":[\"EventTarget\"],\"NodeFilter\":[],\"NodeIterator\":[],\"NodeList\":[],\"Notification\":[\"EventTarget\"],\"NotificationAction\":[],\"NotificationDirection\":[],\"NotificationEvent\":[\"Event\",\"ExtendableEvent\"],\"NotificationEventInit\":[],\"NotificationOptions\":[],\"NotificationPermission\":[],\"ObserverCallback\":[],\"OesElementIndexUint\":[],\"OesStandardDerivatives\":[],\"OesTextureFloat\":[],\"OesTextureFloatLinear\":[],\"OesTextureHalfFloat\":[],\"OesTextureHalfFloatLinear\":[],\"OesVertexArrayObject\":[],\"OfflineAudioCompletionEvent\":[\"Event\"],\"OfflineAudioCompletionEventInit\":[],\"OfflineAudioContext\":[\"BaseAudioContext\",\"EventTarget\"],\"OfflineAudioContextOptions\":[],\"OfflineResourceList\":[\"EventTarget\"],\"OffscreenCanvas\":[\"EventTarget\"],\"OffscreenCanvasRenderingContext2d\":[],\"OpenFilePickerOptions\":[],\"OpenWindowEventDetail\":[],\"OptionalEffectTiming\":[],\"OrientationLockType\":[],\"OrientationType\":[],\"OscillatorNode\":[\"AudioNode\",\"AudioScheduledSourceNode\",\"EventTarget\"],\"OscillatorOptions\":[],\"OscillatorType\":[],\"OverSampleType\":[],\"OvrMultiview2\":[],\"PageTransitionEvent\":[\"Event\"],\"PageTransitionEventInit\":[],\"PaintRequest\":[],\"PaintRequestList\":[],\"PaintWorkletGlobalScope\":[\"WorkletGlobalScope\"],\"PannerNode\":[\"AudioNode\",\"EventTarget\"],\"PannerOptions\":[],\"PanningModelType\":[],\"ParityType\":[],\"Path2d\":[],\"PaymentAddress\":[],\"PaymentComplete\":[],\"PaymentMethodChangeEvent\":[\"Event\",\"PaymentRequestUpdateEvent\"],\"PaymentMethodChangeEventInit\":[],\"PaymentRequestUpdateEvent\":[\"Event\"],\"PaymentRequestUpdateEventInit\":[],\"PaymentResponse\":[],\"Pbkdf2Params\":[],\"PcImplIceConnectionState\":[],\"PcImplIceGatheringState\":[],\"PcImplSignalingState\":[],\"PcObserverStateType\":[],\"Performance\":[\"EventTarget\"],\"PerformanceEntry\":[],\"PerformanceEntryEventInit\":[],\"PerformanceEntryFilterOptions\":[],\"PerformanceMark\":[\"PerformanceEntry\"],\"PerformanceMeasure\":[\"PerformanceEntry\"],\"PerformanceNavigation\":[],\"PerformanceNavigationTiming\":[\"PerformanceEntry\",\"PerformanceResourceTiming\"],\"PerformanceObserver\":[],\"PerformanceObserverEntryList\":[],\"PerformanceObserverInit\":[],\"PerformanceResourceTiming\":[\"PerformanceEntry\"],\"PerformanceServerTiming\":[],\"PerformanceTiming\":[],\"PeriodicWave\":[],\"PeriodicWaveConstraints\":[],\"PeriodicWaveOptions\":[],\"PermissionDescriptor\":[],\"PermissionName\":[],\"PermissionState\":[],\"PermissionStatus\":[\"EventTarget\"],\"Permissions\":[],\"PictureInPictureEvent\":[\"Event\"],\"PictureInPictureEventInit\":[],\"PictureInPictureWindow\":[\"EventTarget\"],\"PlaneLayout\":[],\"PlaybackDirection\":[],\"Plugin\":[],\"PluginArray\":[],\"PluginCrashedEventInit\":[],\"PointerEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"PointerEventInit\":[],\"PopStateEvent\":[\"Event\"],\"PopStateEventInit\":[],\"PopupBlockedEvent\":[\"Event\"],\"PopupBlockedEventInit\":[],\"Position\":[],\"PositionAlignSetting\":[],\"PositionError\":[],\"PositionOptions\":[],\"PremultiplyAlpha\":[],\"Presentation\":[],\"PresentationAvailability\":[\"EventTarget\"],\"PresentationConnection\":[\"EventTarget\"],\"PresentationConnectionAvailableEvent\":[\"Event\"],\"PresentationConnectionAvailableEventInit\":[],\"PresentationConnectionBinaryType\":[],\"PresentationConnectionCloseEvent\":[\"Event\"],\"PresentationConnectionCloseEventInit\":[],\"PresentationConnectionClosedReason\":[],\"PresentationConnectionList\":[\"EventTarget\"],\"PresentationConnectionState\":[],\"PresentationReceiver\":[],\"PresentationRequest\":[\"EventTarget\"],\"PresentationStyle\":[],\"ProcessingInstruction\":[\"CharacterData\",\"EventTarget\",\"Node\"],\"ProfileTimelineLayerRect\":[],\"ProfileTimelineMarker\":[],\"ProfileTimelineMessagePortOperationType\":[],\"ProfileTimelineStackFrame\":[],\"ProfileTimelineWorkerOperationType\":[],\"ProgressEvent\":[\"Event\"],\"ProgressEventInit\":[],\"PromiseNativeHandler\":[],\"PromiseRejectionEvent\":[\"Event\"],\"PromiseRejectionEventInit\":[],\"PublicKeyCredential\":[\"Credential\"],\"PublicKeyCredentialCreationOptions\":[],\"PublicKeyCredentialCreationOptionsJson\":[],\"PublicKeyCredentialDescriptor\":[],\"PublicKeyCredentialDescriptorJson\":[],\"PublicKeyCredentialEntity\":[],\"PublicKeyCredentialHints\":[],\"PublicKeyCredentialParameters\":[],\"PublicKeyCredentialRequestOptions\":[],\"PublicKeyCredentialRequestOptionsJson\":[],\"PublicKeyCredentialRpEntity\":[],\"PublicKeyCredentialType\":[],\"PublicKeyCredentialUserEntity\":[],\"PublicKeyCredentialUserEntityJson\":[],\"PushEncryptionKeyName\":[],\"PushEvent\":[\"Event\",\"ExtendableEvent\"],\"PushEventInit\":[],\"PushManager\":[],\"PushMessageData\":[],\"PushPermissionState\":[],\"PushSubscription\":[],\"PushSubscriptionInit\":[],\"PushSubscriptionJson\":[],\"PushSubscriptionKeys\":[],\"PushSubscriptionOptions\":[],\"PushSubscriptionOptionsInit\":[],\"QueryOptions\":[],\"QueuingStrategy\":[],\"QueuingStrategyInit\":[],\"RadioNodeList\":[\"NodeList\"],\"Range\":[],\"RcwnPerfStats\":[],\"RcwnStatus\":[],\"ReadableByteStreamController\":[],\"ReadableStream\":[],\"ReadableStreamByobReader\":[],\"ReadableStreamByobRequest\":[],\"ReadableStreamDefaultController\":[],\"ReadableStreamDefaultReader\":[],\"ReadableStreamGetReaderOptions\":[],\"ReadableStreamIteratorOptions\":[],\"ReadableStreamReadResult\":[],\"ReadableStreamReaderMode\":[],\"ReadableStreamType\":[],\"ReadableWritablePair\":[],\"RecordingState\":[],\"ReferrerPolicy\":[],\"RegisterRequest\":[],\"RegisterResponse\":[],\"RegisteredKey\":[],\"RegistrationOptions\":[],\"RegistrationResponseJson\":[],\"Request\":[],\"RequestCache\":[],\"RequestCredentials\":[],\"RequestDestination\":[],\"RequestDeviceOptions\":[],\"RequestInit\":[],\"RequestMediaKeySystemAccessNotification\":[],\"RequestMode\":[],\"RequestRedirect\":[],\"ResidentKeyRequirement\":[],\"ResizeObserver\":[],\"ResizeObserverBoxOptions\":[],\"ResizeObserverEntry\":[],\"ResizeObserverOptions\":[],\"ResizeObserverSize\":[],\"ResizeQuality\":[],\"Response\":[],\"ResponseInit\":[],\"ResponseType\":[],\"RsaHashedImportParams\":[],\"RsaOaepParams\":[],\"RsaOtherPrimesInfo\":[],\"RsaPssParams\":[],\"RtcAnswerOptions\":[],\"RtcBundlePolicy\":[],\"RtcCertificate\":[],\"RtcCertificateExpiration\":[],\"RtcCodecStats\":[],\"RtcConfiguration\":[],\"RtcDataChannel\":[\"EventTarget\"],\"RtcDataChannelEvent\":[\"Event\"],\"RtcDataChannelEventInit\":[],\"RtcDataChannelInit\":[],\"RtcDataChannelState\":[],\"RtcDataChannelType\":[],\"RtcDegradationPreference\":[],\"RtcEncodedAudioFrame\":[],\"RtcEncodedAudioFrameMetadata\":[],\"RtcEncodedAudioFrameOptions\":[],\"RtcEncodedVideoFrame\":[],\"RtcEncodedVideoFrameMetadata\":[],\"RtcEncodedVideoFrameOptions\":[],\"RtcEncodedVideoFrameType\":[],\"RtcFecParameters\":[],\"RtcIceCandidate\":[],\"RtcIceCandidateInit\":[],\"RtcIceCandidatePairStats\":[],\"RtcIceCandidateStats\":[],\"RtcIceComponentStats\":[],\"RtcIceConnectionState\":[],\"RtcIceCredentialType\":[],\"RtcIceGatheringState\":[],\"RtcIceServer\":[],\"RtcIceTransportPolicy\":[],\"RtcIdentityAssertion\":[],\"RtcIdentityAssertionResult\":[],\"RtcIdentityProvider\":[],\"RtcIdentityProviderDetails\":[],\"RtcIdentityProviderOptions\":[],\"RtcIdentityProviderRegistrar\":[],\"RtcIdentityValidationResult\":[],\"RtcInboundRtpStreamStats\":[],\"RtcMediaStreamStats\":[],\"RtcMediaStreamTrackStats\":[],\"RtcOfferAnswerOptions\":[],\"RtcOfferOptions\":[],\"RtcOutboundRtpStreamStats\":[],\"RtcPeerConnection\":[\"EventTarget\"],\"RtcPeerConnectionIceErrorEvent\":[\"Event\"],\"RtcPeerConnectionIceEvent\":[\"Event\"],\"RtcPeerConnectionIceEventInit\":[],\"RtcPeerConnectionState\":[],\"RtcPriorityType\":[],\"RtcRtcpParameters\":[],\"RtcRtpCapabilities\":[],\"RtcRtpCodecCapability\":[],\"RtcRtpCodecParameters\":[],\"RtcRtpContributingSource\":[],\"RtcRtpEncodingParameters\":[],\"RtcRtpHeaderExtensionCapability\":[],\"RtcRtpHeaderExtensionParameters\":[],\"RtcRtpParameters\":[],\"RtcRtpReceiver\":[],\"RtcRtpScriptTransform\":[],\"RtcRtpScriptTransformer\":[\"EventTarget\"],\"RtcRtpSender\":[],\"RtcRtpSourceEntry\":[],\"RtcRtpSourceEntryType\":[],\"RtcRtpSynchronizationSource\":[],\"RtcRtpTransceiver\":[],\"RtcRtpTransceiverDirection\":[],\"RtcRtpTransceiverInit\":[],\"RtcRtxParameters\":[],\"RtcSdpType\":[],\"RtcSessionDescription\":[],\"RtcSessionDescriptionInit\":[],\"RtcSignalingState\":[],\"RtcStats\":[],\"RtcStatsIceCandidatePairState\":[],\"RtcStatsIceCandidateType\":[],\"RtcStatsReport\":[],\"RtcStatsReportInternal\":[],\"RtcStatsType\":[],\"RtcTrackEvent\":[\"Event\"],\"RtcTrackEventInit\":[],\"RtcTransformEvent\":[\"Event\"],\"RtcTransportStats\":[],\"RtcdtmfSender\":[\"EventTarget\"],\"RtcdtmfToneChangeEvent\":[\"Event\"],\"RtcdtmfToneChangeEventInit\":[],\"RtcrtpContributingSourceStats\":[],\"RtcrtpStreamStats\":[],\"SFrameTransform\":[\"EventTarget\"],\"SFrameTransformErrorEvent\":[\"Event\"],\"SFrameTransformErrorEventInit\":[],\"SFrameTransformErrorEventType\":[],\"SFrameTransformOptions\":[],\"SFrameTransformRole\":[],\"SaveFilePickerOptions\":[],\"Scheduler\":[],\"SchedulerPostTaskOptions\":[],\"Scheduling\":[],\"Screen\":[\"EventTarget\"],\"ScreenColorGamut\":[],\"ScreenDetailed\":[\"EventTarget\",\"Screen\"],\"ScreenDetails\":[\"EventTarget\"],\"ScreenLuminance\":[],\"ScreenOrientation\":[\"EventTarget\"],\"ScriptProcessorNode\":[\"AudioNode\",\"EventTarget\"],\"ScrollAreaEvent\":[\"Event\",\"UiEvent\"],\"ScrollBehavior\":[],\"ScrollBoxObject\":[],\"ScrollIntoViewContainer\":[],\"ScrollIntoViewOptions\":[],\"ScrollLogicalPosition\":[],\"ScrollOptions\":[],\"ScrollRestoration\":[],\"ScrollSetting\":[],\"ScrollState\":[],\"ScrollToOptions\":[],\"ScrollViewChangeEventInit\":[],\"SecurityPolicyViolationEvent\":[\"Event\"],\"SecurityPolicyViolationEventDisposition\":[],\"SecurityPolicyViolationEventInit\":[],\"Selection\":[],\"SelectionMode\":[],\"Serial\":[\"EventTarget\"],\"SerialInputSignals\":[],\"SerialOptions\":[],\"SerialOutputSignals\":[],\"SerialPort\":[\"EventTarget\"],\"SerialPortFilter\":[],\"SerialPortInfo\":[],\"SerialPortRequestOptions\":[],\"ServerSocketOptions\":[],\"ServiceWorker\":[\"EventTarget\"],\"ServiceWorkerContainer\":[\"EventTarget\"],\"ServiceWorkerGlobalScope\":[\"EventTarget\",\"WorkerGlobalScope\"],\"ServiceWorkerRegistration\":[\"EventTarget\"],\"ServiceWorkerState\":[],\"ServiceWorkerUpdateViaCache\":[],\"ShadowRoot\":[\"DocumentFragment\",\"EventTarget\",\"Node\"],\"ShadowRootInit\":[],\"ShadowRootMode\":[],\"ShareData\":[],\"SharedWorker\":[\"EventTarget\"],\"SharedWorkerGlobalScope\":[\"EventTarget\",\"WorkerGlobalScope\"],\"SignResponse\":[],\"SocketElement\":[],\"SocketOptions\":[],\"SocketReadyState\":[],\"SocketsDict\":[],\"SourceBuffer\":[\"EventTarget\"],\"SourceBufferAppendMode\":[],\"SourceBufferList\":[\"EventTarget\"],\"SpeechGrammar\":[],\"SpeechGrammarList\":[],\"SpeechRecognition\":[\"EventTarget\"],\"SpeechRecognitionAlternative\":[],\"SpeechRecognitionError\":[\"Event\"],\"SpeechRecognitionErrorCode\":[],\"SpeechRecognitionErrorInit\":[],\"SpeechRecognitionEvent\":[\"Event\"],\"SpeechRecognitionEventInit\":[],\"SpeechRecognitionResult\":[],\"SpeechRecognitionResultList\":[],\"SpeechSynthesis\":[\"EventTarget\"],\"SpeechSynthesisErrorCode\":[],\"SpeechSynthesisErrorEvent\":[\"Event\",\"SpeechSynthesisEvent\"],\"SpeechSynthesisErrorEventInit\":[],\"SpeechSynthesisEvent\":[\"Event\"],\"SpeechSynthesisEventInit\":[],\"SpeechSynthesisUtterance\":[\"EventTarget\"],\"SpeechSynthesisVoice\":[],\"StereoPannerNode\":[\"AudioNode\",\"EventTarget\"],\"StereoPannerOptions\":[],\"Storage\":[],\"StorageEstimate\":[],\"StorageEvent\":[\"Event\"],\"StorageEventInit\":[],\"StorageManager\":[],\"StorageType\":[],\"StreamPipeOptions\":[],\"StyleRuleChangeEventInit\":[],\"StyleSheet\":[],\"StyleSheetApplicableStateChangeEventInit\":[],\"StyleSheetChangeEventInit\":[],\"StyleSheetList\":[],\"SubmitEvent\":[\"Event\"],\"SubmitEventInit\":[],\"SubtleCrypto\":[],\"SupportedType\":[],\"SvcOutputMetadata\":[],\"SvgAngle\":[],\"SvgAnimateElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgAnimateMotionElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgAnimateTransformElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgAnimatedAngle\":[],\"SvgAnimatedBoolean\":[],\"SvgAnimatedEnumeration\":[],\"SvgAnimatedInteger\":[],\"SvgAnimatedLength\":[],\"SvgAnimatedLengthList\":[],\"SvgAnimatedNumber\":[],\"SvgAnimatedNumberList\":[],\"SvgAnimatedPreserveAspectRatio\":[],\"SvgAnimatedRect\":[],\"SvgAnimatedString\":[],\"SvgAnimatedTransformList\":[],\"SvgAnimationElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgBoundingBoxOptions\":[],\"SvgCircleElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgClipPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgComponentTransferFunctionElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgDefsElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgDescElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgElement\":[\"Element\",\"EventTarget\",\"Node\"],\"SvgEllipseElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgFilterElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgForeignObjectElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgGeometryElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgGradientElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgGraphicsElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgImageElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgLength\":[],\"SvgLengthList\":[],\"SvgLineElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgLinearGradientElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGradientElement\"],\"SvgMarkerElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgMaskElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgMatrix\":[],\"SvgMetadataElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgNumber\":[],\"SvgNumberList\":[],\"SvgPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgPathSeg\":[],\"SvgPathSegArcAbs\":[\"SvgPathSeg\"],\"SvgPathSegArcRel\":[\"SvgPathSeg\"],\"SvgPathSegClosePath\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicRel\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicSmoothAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoCubicSmoothRel\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticRel\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticSmoothAbs\":[\"SvgPathSeg\"],\"SvgPathSegCurvetoQuadraticSmoothRel\":[\"SvgPathSeg\"],\"SvgPathSegLinetoAbs\":[\"SvgPathSeg\"],\"SvgPathSegLinetoHorizontalAbs\":[\"SvgPathSeg\"],\"SvgPathSegLinetoHorizontalRel\":[\"SvgPathSeg\"],\"SvgPathSegLinetoRel\":[\"SvgPathSeg\"],\"SvgPathSegLinetoVerticalAbs\":[\"SvgPathSeg\"],\"SvgPathSegLinetoVerticalRel\":[\"SvgPathSeg\"],\"SvgPathSegList\":[],\"SvgPathSegMovetoAbs\":[\"SvgPathSeg\"],\"SvgPathSegMovetoRel\":[\"SvgPathSeg\"],\"SvgPatternElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgPoint\":[],\"SvgPointList\":[],\"SvgPolygonElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgPolylineElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgPreserveAspectRatio\":[],\"SvgRadialGradientElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGradientElement\"],\"SvgRect\":[],\"SvgRectElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGeometryElement\",\"SvgGraphicsElement\"],\"SvgScriptElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgSetElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgAnimationElement\",\"SvgElement\"],\"SvgStopElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgStringList\":[],\"SvgStyleElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgSwitchElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgSymbolElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgTextContentElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgTextElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\",\"SvgTextPositioningElement\"],\"SvgTextPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\"],\"SvgTextPositioningElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\"],\"SvgTitleElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgTransform\":[],\"SvgTransformList\":[],\"SvgUnitTypes\":[],\"SvgUseElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgViewElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgZoomAndPan\":[],\"SvgaElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgfeBlendElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeColorMatrixElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeComponentTransferElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeCompositeElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeConvolveMatrixElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDiffuseLightingElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDisplacementMapElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDistantLightElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeDropShadowElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeFloodElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeFuncAElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeFuncBElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeFuncGElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeFuncRElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgComponentTransferFunctionElement\",\"SvgElement\"],\"SvgfeGaussianBlurElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeImageElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeMergeElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeMergeNodeElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeMorphologyElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeOffsetElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfePointLightElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeSpecularLightingElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeSpotLightElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeTileElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgfeTurbulenceElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvggElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgmPathElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\"],\"SvgsvgElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\"],\"SvgtSpanElement\":[\"Element\",\"EventTarget\",\"Node\",\"SvgElement\",\"SvgGraphicsElement\",\"SvgTextContentElement\",\"SvgTextPositioningElement\"],\"TaskController\":[\"AbortController\"],\"TaskControllerInit\":[],\"TaskPriority\":[],\"TaskPriorityChangeEvent\":[\"Event\"],\"TaskPriorityChangeEventInit\":[],\"TaskSignal\":[\"AbortSignal\",\"EventTarget\"],\"TaskSignalAnyInit\":[],\"TcpReadyState\":[],\"TcpServerSocket\":[\"EventTarget\"],\"TcpServerSocketEvent\":[\"Event\"],\"TcpServerSocketEventInit\":[],\"TcpSocket\":[\"EventTarget\"],\"TcpSocketBinaryType\":[],\"TcpSocketErrorEvent\":[\"Event\"],\"TcpSocketErrorEventInit\":[],\"TcpSocketEvent\":[\"Event\"],\"TcpSocketEventInit\":[],\"Text\":[\"CharacterData\",\"EventTarget\",\"Node\"],\"TextDecodeOptions\":[],\"TextDecoder\":[],\"TextDecoderOptions\":[],\"TextEncoder\":[],\"TextMetrics\":[],\"TextTrack\":[\"EventTarget\"],\"TextTrackCue\":[\"EventTarget\"],\"TextTrackCueList\":[],\"TextTrackKind\":[],\"TextTrackList\":[\"EventTarget\"],\"TextTrackMode\":[],\"TimeEvent\":[\"Event\"],\"TimeRanges\":[],\"ToggleEvent\":[\"Event\"],\"ToggleEventInit\":[],\"TokenBinding\":[],\"TokenBindingStatus\":[],\"Touch\":[],\"TouchEvent\":[\"Event\",\"UiEvent\"],\"TouchEventInit\":[],\"TouchInit\":[],\"TouchList\":[],\"TrackEvent\":[\"Event\"],\"TrackEventInit\":[],\"TransformStream\":[],\"TransformStreamDefaultController\":[],\"Transformer\":[],\"TransitionEvent\":[\"Event\"],\"TransitionEventInit\":[],\"Transport\":[],\"TreeBoxObject\":[],\"TreeCellInfo\":[],\"TreeView\":[],\"TreeWalker\":[],\"U2f\":[],\"U2fClientData\":[],\"ULongRange\":[],\"UaDataValues\":[],\"UaLowEntropyJson\":[],\"UdpMessageEventInit\":[],\"UdpOptions\":[],\"UiEvent\":[\"Event\"],\"UiEventInit\":[],\"UnderlyingSink\":[],\"UnderlyingSource\":[],\"Url\":[],\"UrlSearchParams\":[],\"Usb\":[\"EventTarget\"],\"UsbAlternateInterface\":[],\"UsbConfiguration\":[],\"UsbConnectionEvent\":[\"Event\"],\"UsbConnectionEventInit\":[],\"UsbControlTransferParameters\":[],\"UsbDevice\":[],\"UsbDeviceFilter\":[],\"UsbDeviceRequestOptions\":[],\"UsbDirection\":[],\"UsbEndpoint\":[],\"UsbEndpointType\":[],\"UsbInTransferResult\":[],\"UsbInterface\":[],\"UsbIsochronousInTransferPacket\":[],\"UsbIsochronousInTransferResult\":[],\"UsbIsochronousOutTransferPacket\":[],\"UsbIsochronousOutTransferResult\":[],\"UsbOutTransferResult\":[],\"UsbPermissionDescriptor\":[],\"UsbPermissionResult\":[\"EventTarget\",\"PermissionStatus\"],\"UsbPermissionStorage\":[],\"UsbRecipient\":[],\"UsbRequestType\":[],\"UsbTransferStatus\":[],\"UserActivation\":[],\"UserProximityEvent\":[\"Event\"],\"UserProximityEventInit\":[],\"UserVerificationRequirement\":[],\"ValidityState\":[],\"ValueEvent\":[\"Event\"],\"ValueEventInit\":[],\"VideoColorPrimaries\":[],\"VideoColorSpace\":[],\"VideoColorSpaceInit\":[],\"VideoConfiguration\":[],\"VideoDecoder\":[],\"VideoDecoderConfig\":[],\"VideoDecoderInit\":[],\"VideoDecoderSupport\":[],\"VideoEncoder\":[],\"VideoEncoderConfig\":[],\"VideoEncoderEncodeOptions\":[],\"VideoEncoderInit\":[],\"VideoEncoderSupport\":[],\"VideoFacingModeEnum\":[],\"VideoFrame\":[],\"VideoFrameBufferInit\":[],\"VideoFrameCopyToOptions\":[],\"VideoFrameInit\":[],\"VideoMatrixCoefficients\":[],\"VideoPixelFormat\":[],\"VideoPlaybackQuality\":[],\"VideoStreamTrack\":[\"EventTarget\",\"MediaStreamTrack\"],\"VideoTrack\":[],\"VideoTrackList\":[\"EventTarget\"],\"VideoTransferCharacteristics\":[],\"ViewTransition\":[],\"VisibilityState\":[],\"VisualViewport\":[\"EventTarget\"],\"VoidCallback\":[],\"VrDisplay\":[\"EventTarget\"],\"VrDisplayCapabilities\":[],\"VrEye\":[],\"VrEyeParameters\":[],\"VrFieldOfView\":[],\"VrFrameData\":[],\"VrLayer\":[],\"VrMockController\":[],\"VrMockDisplay\":[],\"VrPose\":[],\"VrServiceTest\":[],\"VrStageParameters\":[],\"VrSubmitFrameResult\":[],\"VttCue\":[\"EventTarget\",\"TextTrackCue\"],\"VttRegion\":[],\"WakeLock\":[],\"WakeLockSentinel\":[\"EventTarget\"],\"WakeLockType\":[],\"WatchAdvertisementsOptions\":[],\"WaveShaperNode\":[\"AudioNode\",\"EventTarget\"],\"WaveShaperOptions\":[],\"WebGl2RenderingContext\":[],\"WebGlActiveInfo\":[],\"WebGlBuffer\":[],\"WebGlContextAttributes\":[],\"WebGlContextEvent\":[\"Event\"],\"WebGlContextEventInit\":[],\"WebGlFramebuffer\":[],\"WebGlPowerPreference\":[],\"WebGlProgram\":[],\"WebGlQuery\":[],\"WebGlRenderbuffer\":[],\"WebGlRenderingContext\":[],\"WebGlSampler\":[],\"WebGlShader\":[],\"WebGlShaderPrecisionFormat\":[],\"WebGlSync\":[],\"WebGlTexture\":[],\"WebGlTransformFeedback\":[],\"WebGlUniformLocation\":[],\"WebGlVertexArrayObject\":[],\"WebKitCssMatrix\":[\"DomMatrix\",\"DomMatrixReadOnly\"],\"WebSocket\":[\"EventTarget\"],\"WebSocketDict\":[],\"WebSocketElement\":[],\"WebTransport\":[],\"WebTransportBidirectionalStream\":[],\"WebTransportCloseInfo\":[],\"WebTransportCongestionControl\":[],\"WebTransportDatagramDuplexStream\":[],\"WebTransportDatagramStats\":[],\"WebTransportError\":[\"DomException\"],\"WebTransportErrorOptions\":[],\"WebTransportErrorSource\":[],\"WebTransportHash\":[],\"WebTransportOptions\":[],\"WebTransportReceiveStream\":[\"ReadableStream\"],\"WebTransportReceiveStreamStats\":[],\"WebTransportReliabilityMode\":[],\"WebTransportSendStream\":[\"WritableStream\"],\"WebTransportSendStreamOptions\":[],\"WebTransportSendStreamStats\":[],\"WebTransportStats\":[],\"WebglColorBufferFloat\":[],\"WebglCompressedTextureAstc\":[],\"WebglCompressedTextureAtc\":[],\"WebglCompressedTextureEtc\":[],\"WebglCompressedTextureEtc1\":[],\"WebglCompressedTexturePvrtc\":[],\"WebglCompressedTextureS3tc\":[],\"WebglCompressedTextureS3tcSrgb\":[],\"WebglDebugRendererInfo\":[],\"WebglDebugShaders\":[],\"WebglDepthTexture\":[],\"WebglDrawBuffers\":[],\"WebglLoseContext\":[],\"WebglMultiDraw\":[],\"WellKnownDirectory\":[],\"WgslLanguageFeatures\":[],\"WheelEvent\":[\"Event\",\"MouseEvent\",\"UiEvent\"],\"WheelEventInit\":[],\"WidevineCdmManifest\":[],\"Window\":[\"EventTarget\"],\"WindowClient\":[\"Client\"],\"Worker\":[\"EventTarget\"],\"WorkerDebuggerGlobalScope\":[\"EventTarget\"],\"WorkerGlobalScope\":[\"EventTarget\"],\"WorkerLocation\":[],\"WorkerNavigator\":[],\"WorkerOptions\":[],\"WorkerType\":[],\"Worklet\":[],\"WorkletGlobalScope\":[],\"WorkletOptions\":[],\"WritableStream\":[],\"WritableStreamDefaultController\":[],\"WritableStreamDefaultWriter\":[],\"WriteCommandType\":[],\"WriteParams\":[],\"XPathExpression\":[],\"XPathNsResolver\":[],\"XPathResult\":[],\"XmlDocument\":[\"Document\",\"EventTarget\",\"Node\"],\"XmlHttpRequest\":[\"EventTarget\",\"XmlHttpRequestEventTarget\"],\"XmlHttpRequestEventTarget\":[\"EventTarget\"],\"XmlHttpRequestResponseType\":[],\"XmlHttpRequestUpload\":[\"EventTarget\",\"XmlHttpRequestEventTarget\"],\"XmlSerializer\":[],\"XrBoundedReferenceSpace\":[\"EventTarget\",\"XrReferenceSpace\",\"XrSpace\"],\"XrEye\":[],\"XrFrame\":[],\"XrHand\":[],\"XrHandJoint\":[],\"XrHandedness\":[],\"XrInputSource\":[],\"XrInputSourceArray\":[],\"XrInputSourceEvent\":[\"Event\"],\"XrInputSourceEventInit\":[],\"XrInputSourcesChangeEvent\":[\"Event\"],\"XrInputSourcesChangeEventInit\":[],\"XrJointPose\":[\"XrPose\"],\"XrJointSpace\":[\"EventTarget\",\"XrSpace\"],\"XrLayer\":[\"EventTarget\"],\"XrPermissionDescriptor\":[],\"XrPermissionStatus\":[\"EventTarget\",\"PermissionStatus\"],\"XrPose\":[],\"XrReferenceSpace\":[\"EventTarget\",\"XrSpace\"],\"XrReferenceSpaceEvent\":[\"Event\"],\"XrReferenceSpaceEventInit\":[],\"XrReferenceSpaceType\":[],\"XrRenderState\":[],\"XrRenderStateInit\":[],\"XrRigidTransform\":[],\"XrSession\":[\"EventTarget\"],\"XrSessionEvent\":[\"Event\"],\"XrSessionEventInit\":[],\"XrSessionInit\":[],\"XrSessionMode\":[],\"XrSessionSupportedPermissionDescriptor\":[],\"XrSpace\":[\"EventTarget\"],\"XrSystem\":[\"EventTarget\"],\"XrTargetRayMode\":[],\"XrView\":[],\"XrViewerPose\":[\"XrPose\"],\"XrViewport\":[],\"XrVisibilityState\":[],\"XrWebGlLayer\":[\"EventTarget\",\"XrLayer\"],\"XrWebGlLayerInit\":[],\"XsltProcessor\":[],\"console\":[],\"css\":[],\"default\":[\"std\"],\"gpu_buffer_usage\":[],\"gpu_color_write\":[],\"gpu_map_mode\":[],\"gpu_shader_stage\":[],\"gpu_texture_usage\":[],\"std\":[\"wasm-bindgen/std\",\"js-sys/std\"]}}", "web-time_1.1.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_feature = \\\"atomics\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_feature = \\\"atomics\\\"))\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"name\":\"js-sys\",\"req\":\"^0.3.20\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"macro\"],\"kind\":\"dev\",\"name\":\"pollster\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"^0.2.70\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"},{\"features\":[\"WorkerGlobalScope\"],\"kind\":\"dev\",\"name\":\"web-sys\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_feature = \\\"atomics\\\"))\"},{\"features\":[\"CssStyleDeclaration\",\"Document\",\"Element\",\"HtmlTableElement\",\"HtmlTableRowElement\",\"Performance\",\"Window\"],\"kind\":\"dev\",\"name\":\"web-sys\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"serde\":[\"dep:serde\"]}}", "webbrowser_1.0.6": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-files\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"actix-web\",\"req\":\"^4\"},{\"name\":\"core-foundation\",\"req\":\"^0.10\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"crossbeam-channel\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.0\"},{\"name\":\"jni\",\"req\":\"^0.21\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"ndk-context\",\"req\":\"^0.1\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"kind\":\"dev\",\"name\":\"ndk-glue\",\"req\":\">=0.3, <=0.7\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"objc2\",\"req\":\"^0.6\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"NSDictionary\",\"NSString\",\"NSURL\"],\"name\":\"objc2-foundation\",\"req\":\"^0.3\",\"target\":\"cfg(any(target_os = \\\"ios\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\"))\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^0.10\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"name\":\"url\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"urlencoding\",\"req\":\"^2.1\"},{\"features\":[\"Window\"],\"name\":\"web-sys\",\"req\":\"^0.3\",\"target\":\"cfg(target_family = \\\"wasm\\\")\"}],\"features\":{\"disable-wsl\":[],\"hardened\":[],\"wasm-console\":[\"web-sys/console\"]}}", - "webpki-root-certs_1.0.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"percent-encoding\",\"req\":\"^2.3\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17.0\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17.0\"}],\"features\":{}}", + "webpki-root-certs_1.0.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"percent-encoding\",\"req\":\"^2.3\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17.0\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17.0\"}],\"features\":{}}", "webpki-roots_0.26.11": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"parent\",\"package\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"percent-encoding\",\"req\":\"^2.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.102\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"yasna\",\"req\":\"^0.5.2\"}],\"features\":{}}", - "webpki-roots_1.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"percent-encoding\",\"req\":\"^2.3\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"yasna\",\"req\":\"^0.5.2\"}],\"features\":{}}", - "weezl_0.1.10": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.12\"},{\"default_features\":false,\"features\":[\"macros\",\"io-util\",\"net\",\"rt\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"compat\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.6.2\"}],\"features\":{\"alloc\":[],\"async\":[\"futures\",\"std\"],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", + "webpki-roots_1.0.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"percent-encoding\",\"req\":\"^2.3\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14.3\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17.0\"},{\"kind\":\"dev\",\"name\":\"yasna\",\"req\":\"^0.5.2\"}],\"features\":{}}", + "weezl_0.1.12": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.12\"},{\"default_features\":false,\"features\":[\"macros\",\"io-util\",\"net\",\"rt\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"compat\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.6.2\"}],\"features\":{\"alloc\":[],\"async\":[\"futures\",\"std\"],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "which_8.0.0": "{\"dependencies\":[{\"name\":\"env_home\",\"optional\":true,\"req\":\"^0.1.0\",\"target\":\"cfg(any(windows, unix, target_os = \\\"redox\\\"))\"},{\"name\":\"regex\",\"optional\":true,\"req\":\"^1.10.2\"},{\"default_features\":false,\"features\":[\"fs\",\"std\"],\"name\":\"rustix\",\"optional\":true,\"req\":\"^1.0.5\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\", target_os = \\\"redox\\\"))\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.9.0\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1.40\"},{\"features\":[\"kernel\"],\"name\":\"winsafe\",\"optional\":true,\"req\":\"^0.0.19\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"real-sys\"],\"real-sys\":[\"dep:env_home\",\"dep:rustix\",\"dep:winsafe\"],\"regex\":[\"dep:regex\"],\"tracing\":[\"dep:tracing\"]}}", "whoami_1.6.1": "{\"dependencies\":[{\"name\":\"libredox\",\"req\":\"^0.1.1\",\"target\":\"cfg(all(target_os = \\\"redox\\\", not(target_arch = \\\"wasm32\\\")))\"},{\"name\":\"wasite\",\"req\":\"^0.1\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"wasi\\\"))\"},{\"features\":[\"Navigator\",\"Document\",\"Window\",\"Location\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\"), not(daku)))\"}],\"features\":{\"default\":[\"web\"],\"web\":[\"web-sys\"]}}", "widestring_1.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"debugger_test\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"debugger_test_parser\",\"req\":\"^0.1\"},{\"features\":[\"Win32_System_Diagnostics_Debug\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.59\"}],\"features\":{\"alloc\":[],\"debugger_visualizer\":[\"alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "wildcard_0.3.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.5\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2.0.3\"},{\"kind\":\"dev\",\"name\":\"toml\",\"req\":\"^0.8.14\"},{\"kind\":\"dev\",\"name\":\"wildmatch\",\"req\":\"^2.3.4\"}],\"features\":{\"fatal-warnings\":[]}}", "wildmatch_2.6.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ntest\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.10.2\"},{\"kind\":\"dev\",\"name\":\"regex-lite\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"serde\":[\"dep:serde\"]}}", "winapi-i686-pc-windows-gnu_0.4.0": "{\"dependencies\":[],\"features\":{}}", - "winapi-util_0.1.9": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_Console\",\"Win32_System_SystemInformation\"],\"name\":\"windows-sys\",\"req\":\">=0.48.0, <=0.59\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "winapi-util_0.1.11": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_Storage_FileSystem\",\"Win32_System_Console\",\"Win32_System_SystemInformation\"],\"name\":\"windows-sys\",\"req\":\">=0.48.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "winapi-x86_64-pc-windows-gnu_0.4.0": "{\"dependencies\":[],\"features\":{}}", "winapi_0.3.9": "{\"dependencies\":[{\"name\":\"winapi-i686-pc-windows-gnu\",\"req\":\"^0.4\",\"target\":\"i686-pc-windows-gnu\"},{\"name\":\"winapi-x86_64-pc-windows-gnu\",\"req\":\"^0.4\",\"target\":\"x86_64-pc-windows-gnu\"}],\"features\":{\"accctrl\":[],\"aclapi\":[],\"activation\":[],\"adhoc\":[],\"appmgmt\":[],\"audioclient\":[],\"audiosessiontypes\":[],\"avrt\":[],\"basetsd\":[],\"bcrypt\":[],\"bits\":[],\"bits10_1\":[],\"bits1_5\":[],\"bits2_0\":[],\"bits2_5\":[],\"bits3_0\":[],\"bits4_0\":[],\"bits5_0\":[],\"bitscfg\":[],\"bitsmsg\":[],\"bluetoothapis\":[],\"bluetoothleapis\":[],\"bthdef\":[],\"bthioctl\":[],\"bthledef\":[],\"bthsdpdef\":[],\"bugcodes\":[],\"cderr\":[],\"cfg\":[],\"cfgmgr32\":[],\"cguid\":[],\"combaseapi\":[],\"coml2api\":[],\"commapi\":[],\"commctrl\":[],\"commdlg\":[],\"commoncontrols\":[],\"consoleapi\":[],\"corecrt\":[],\"corsym\":[],\"d2d1\":[],\"d2d1_1\":[],\"d2d1_2\":[],\"d2d1_3\":[],\"d2d1effectauthor\":[],\"d2d1effects\":[],\"d2d1effects_1\":[],\"d2d1effects_2\":[],\"d2d1svg\":[],\"d2dbasetypes\":[],\"d3d\":[],\"d3d10\":[],\"d3d10_1\":[],\"d3d10_1shader\":[],\"d3d10effect\":[],\"d3d10misc\":[],\"d3d10sdklayers\":[],\"d3d10shader\":[],\"d3d11\":[],\"d3d11_1\":[],\"d3d11_2\":[],\"d3d11_3\":[],\"d3d11_4\":[],\"d3d11on12\":[],\"d3d11sdklayers\":[],\"d3d11shader\":[],\"d3d11tokenizedprogramformat\":[],\"d3d12\":[],\"d3d12sdklayers\":[],\"d3d12shader\":[],\"d3d9\":[],\"d3d9caps\":[],\"d3d9types\":[],\"d3dcommon\":[],\"d3dcompiler\":[],\"d3dcsx\":[],\"d3dkmdt\":[],\"d3dkmthk\":[],\"d3dukmdt\":[],\"d3dx10core\":[],\"d3dx10math\":[],\"d3dx10mesh\":[],\"datetimeapi\":[],\"davclnt\":[],\"dbghelp\":[],\"dbt\":[],\"dcommon\":[],\"dcomp\":[],\"dcompanimation\":[],\"dcomptypes\":[],\"dde\":[],\"ddraw\":[],\"ddrawi\":[],\"ddrawint\":[],\"debug\":[\"impl-debug\"],\"debugapi\":[],\"devguid\":[],\"devicetopology\":[],\"devpkey\":[],\"devpropdef\":[],\"dinput\":[],\"dinputd\":[],\"dispex\":[],\"dmksctl\":[],\"dmusicc\":[],\"docobj\":[],\"documenttarget\":[],\"dot1x\":[],\"dpa_dsa\":[],\"dpapi\":[],\"dsgetdc\":[],\"dsound\":[],\"dsrole\":[],\"dvp\":[],\"dwmapi\":[],\"dwrite\":[],\"dwrite_1\":[],\"dwrite_2\":[],\"dwrite_3\":[],\"dxdiag\":[],\"dxfile\":[],\"dxgi\":[],\"dxgi1_2\":[],\"dxgi1_3\":[],\"dxgi1_4\":[],\"dxgi1_5\":[],\"dxgi1_6\":[],\"dxgidebug\":[],\"dxgiformat\":[],\"dxgitype\":[],\"dxva2api\":[],\"dxvahd\":[],\"eaptypes\":[],\"enclaveapi\":[],\"endpointvolume\":[],\"errhandlingapi\":[],\"everything\":[],\"evntcons\":[],\"evntprov\":[],\"evntrace\":[],\"excpt\":[],\"exdisp\":[],\"fibersapi\":[],\"fileapi\":[],\"functiondiscoverykeys_devpkey\":[],\"gl-gl\":[],\"guiddef\":[],\"handleapi\":[],\"heapapi\":[],\"hidclass\":[],\"hidpi\":[],\"hidsdi\":[],\"hidusage\":[],\"highlevelmonitorconfigurationapi\":[],\"hstring\":[],\"http\":[],\"ifdef\":[],\"ifmib\":[],\"imm\":[],\"impl-debug\":[],\"impl-default\":[],\"in6addr\":[],\"inaddr\":[],\"inspectable\":[],\"interlockedapi\":[],\"intsafe\":[],\"ioapiset\":[],\"ipexport\":[],\"iphlpapi\":[],\"ipifcons\":[],\"ipmib\":[],\"iprtrmib\":[],\"iptypes\":[],\"jobapi\":[],\"jobapi2\":[],\"knownfolders\":[],\"ks\":[],\"ksmedia\":[],\"ktmtypes\":[],\"ktmw32\":[],\"l2cmn\":[],\"libloaderapi\":[],\"limits\":[],\"lmaccess\":[],\"lmalert\":[],\"lmapibuf\":[],\"lmat\":[],\"lmcons\":[],\"lmdfs\":[],\"lmerrlog\":[],\"lmjoin\":[],\"lmmsg\":[],\"lmremutl\":[],\"lmrepl\":[],\"lmserver\":[],\"lmshare\":[],\"lmstats\":[],\"lmsvc\":[],\"lmuse\":[],\"lmwksta\":[],\"lowlevelmonitorconfigurationapi\":[],\"lsalookup\":[],\"memoryapi\":[],\"minschannel\":[],\"minwinbase\":[],\"minwindef\":[],\"mmdeviceapi\":[],\"mmeapi\":[],\"mmreg\":[],\"mmsystem\":[],\"mprapidef\":[],\"msaatext\":[],\"mscat\":[],\"mschapp\":[],\"mssip\":[],\"mstcpip\":[],\"mswsock\":[],\"mswsockdef\":[],\"namedpipeapi\":[],\"namespaceapi\":[],\"nb30\":[],\"ncrypt\":[],\"netioapi\":[],\"nldef\":[],\"ntddndis\":[],\"ntddscsi\":[],\"ntddser\":[],\"ntdef\":[],\"ntlsa\":[],\"ntsecapi\":[],\"ntstatus\":[],\"oaidl\":[],\"objbase\":[],\"objidl\":[],\"objidlbase\":[],\"ocidl\":[],\"ole2\":[],\"oleauto\":[],\"olectl\":[],\"oleidl\":[],\"opmapi\":[],\"pdh\":[],\"perflib\":[],\"physicalmonitorenumerationapi\":[],\"playsoundapi\":[],\"portabledevice\":[],\"portabledeviceapi\":[],\"portabledevicetypes\":[],\"powerbase\":[],\"powersetting\":[],\"powrprof\":[],\"processenv\":[],\"processsnapshot\":[],\"processthreadsapi\":[],\"processtopologyapi\":[],\"profileapi\":[],\"propidl\":[],\"propkey\":[],\"propkeydef\":[],\"propsys\":[],\"prsht\":[],\"psapi\":[],\"qos\":[],\"realtimeapiset\":[],\"reason\":[],\"restartmanager\":[],\"restrictederrorinfo\":[],\"rmxfguid\":[],\"roapi\":[],\"robuffer\":[],\"roerrorapi\":[],\"rpc\":[],\"rpcdce\":[],\"rpcndr\":[],\"rtinfo\":[],\"sapi\":[],\"sapi51\":[],\"sapi53\":[],\"sapiddk\":[],\"sapiddk51\":[],\"schannel\":[],\"sddl\":[],\"securityappcontainer\":[],\"securitybaseapi\":[],\"servprov\":[],\"setupapi\":[],\"shellapi\":[],\"shellscalingapi\":[],\"shlobj\":[],\"shobjidl\":[],\"shobjidl_core\":[],\"shtypes\":[],\"softpub\":[],\"spapidef\":[],\"spellcheck\":[],\"sporder\":[],\"sql\":[],\"sqlext\":[],\"sqltypes\":[],\"sqlucode\":[],\"sspi\":[],\"std\":[],\"stralign\":[],\"stringapiset\":[],\"strmif\":[],\"subauth\":[],\"synchapi\":[],\"sysinfoapi\":[],\"systemtopologyapi\":[],\"taskschd\":[],\"tcpestats\":[],\"tcpmib\":[],\"textstor\":[],\"threadpoolapiset\":[],\"threadpoollegacyapiset\":[],\"timeapi\":[],\"timezoneapi\":[],\"tlhelp32\":[],\"transportsettingcommon\":[],\"tvout\":[],\"udpmib\":[],\"unknwnbase\":[],\"urlhist\":[],\"urlmon\":[],\"usb\":[],\"usbioctl\":[],\"usbiodef\":[],\"usbscan\":[],\"usbspec\":[],\"userenv\":[],\"usp10\":[],\"utilapiset\":[],\"uxtheme\":[],\"vadefs\":[],\"vcruntime\":[],\"vsbackup\":[],\"vss\":[],\"vsserror\":[],\"vswriter\":[],\"wbemads\":[],\"wbemcli\":[],\"wbemdisp\":[],\"wbemprov\":[],\"wbemtran\":[],\"wct\":[],\"werapi\":[],\"winbase\":[],\"wincodec\":[],\"wincodecsdk\":[],\"wincon\":[],\"wincontypes\":[],\"wincred\":[],\"wincrypt\":[],\"windef\":[],\"windot11\":[],\"windowsceip\":[],\"windowsx\":[],\"winefs\":[],\"winerror\":[],\"winevt\":[],\"wingdi\":[],\"winhttp\":[],\"wininet\":[],\"winineti\":[],\"winioctl\":[],\"winnetwk\":[],\"winnls\":[],\"winnt\":[],\"winreg\":[],\"winsafer\":[],\"winscard\":[],\"winsmcrd\":[],\"winsock2\":[],\"winspool\":[],\"winstring\":[],\"winsvc\":[],\"wintrust\":[],\"winusb\":[],\"winusbio\":[],\"winuser\":[],\"winver\":[],\"wlanapi\":[],\"wlanihv\":[],\"wlanihvtypes\":[],\"wlantypes\":[],\"wlclient\":[],\"wmistr\":[],\"wnnc\":[],\"wow64apiset\":[],\"wpdmtpextensions\":[],\"ws2bth\":[],\"ws2def\":[],\"ws2ipdef\":[],\"ws2spi\":[],\"ws2tcpip\":[],\"wtsapi32\":[],\"wtypes\":[],\"wtypesbase\":[],\"xinput\":[]}}", - "windows-collections_0.2.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.61.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"windows-result\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"windows-strings\",\"req\":\"^0.4.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "windows-collections_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.62.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"windows-strings\",\"req\":\"^0.5.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-core/std\"]}}", "windows-core_0.58.0": "{\"dependencies\":[{\"name\":\"windows-implement\",\"req\":\"^0.58.0\"},{\"name\":\"windows-interface\",\"req\":\"^0.58.0\"},{\"name\":\"windows-result\",\"req\":\"^0.2.0\"},{\"name\":\"windows-strings\",\"req\":\"^0.1.0\"},{\"name\":\"windows-targets\",\"req\":\"^0.52.6\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "windows-core_0.61.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-implement\",\"req\":\"^0.60.0\"},{\"default_features\":false,\"name\":\"windows-interface\",\"req\":\"^0.59.1\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.1\"},{\"default_features\":false,\"name\":\"windows-result\",\"req\":\"^0.3.4\"},{\"default_features\":false,\"name\":\"windows-strings\",\"req\":\"^0.4.2\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-result/std\",\"windows-strings/std\"]}}", - "windows-future_0.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.61.1\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"windows-result\",\"req\":\"^0.3.3\"},{\"default_features\":false,\"name\":\"windows-threading\",\"req\":\"^0.1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "windows-core_0.62.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-implement\",\"req\":\"^0.60.2\"},{\"default_features\":false,\"name\":\"windows-interface\",\"req\":\"^0.59.3\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"windows-result\",\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"windows-strings\",\"req\":\"^0.5.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-result/std\",\"windows-strings/std\"]}}", + "windows-future_0.3.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.62.2\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"windows-threading\",\"req\":\"^0.2.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-core/std\"]}}", "windows-implement_0.58.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\",\"printing\",\"full\",\"derive\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", - "windows-implement_0.60.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\",\"printing\",\"full\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", + "windows-implement_0.60.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\",\"printing\",\"full\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "windows-interface_0.58.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\",\"printing\",\"full\",\"derive\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", - "windows-interface_0.59.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\",\"printing\",\"full\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", - "windows-link_0.1.3": "{\"dependencies\":[],\"features\":{}}", - "windows-link_0.2.0": "{\"dependencies\":[],\"features\":{}}", - "windows-numerics_0.2.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.61.0\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "windows-registry_0.5.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"windows-result\",\"req\":\"^0.3.4\"},{\"default_features\":false,\"name\":\"windows-strings\",\"req\":\"^0.4.2\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-result/std\",\"windows-strings/std\"]}}", + "windows-interface_0.59.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\",\"printing\",\"full\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", + "windows-link_0.2.1": "{\"dependencies\":[],\"features\":{}}", + "windows-numerics_0.3.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.62.2\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-core/std\"]}}", + "windows-registry_0.6.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"windows-result\",\"req\":\"^0.4.1\"},{\"default_features\":false,\"name\":\"windows-strings\",\"req\":\"^0.5.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"windows-result/std\",\"windows-strings/std\"]}}", "windows-result_0.2.0": "{\"dependencies\":[{\"name\":\"windows-targets\",\"req\":\"^0.52.6\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "windows-result_0.3.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "windows-result_0.4.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "windows-strings_0.1.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-result\",\"req\":\"^0.2.0\"},{\"name\":\"windows-targets\",\"req\":\"^0.52.6\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", - "windows-strings_0.4.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "windows-strings_0.5.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "windows-sys_0.45.0": "{\"dependencies\":[{\"name\":\"windows-targets\",\"req\":\"^0.42.1\",\"target\":\"cfg(not(windows_raw_dylib))\"}],\"features\":{\"Win32\":[],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Data_Xml\":[\"Win32_Data\"],\"Win32_Data_Xml_MsXml\":[\"Win32_Data_Xml\"],\"Win32_Data_Xml_XmlLite\":[\"Win32_Data_Xml\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAccess\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_FunctionDiscovery\":[\"Win32_Devices\"],\"Win32_Devices_Geolocation\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_ImageAcquisition\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_Audio_Apo\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectMusic\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_Endpoints\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_XAudio2\":[\"Win32_Media_Audio\"],\"Win32_Media_DeviceManager\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_LibrarySharingServices\":[\"Win32_Media\"],\"Win32_Media_MediaPlayer\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Speech\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_MobileBroadband\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkPolicyServer\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectNow\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_BackgroundIntelligentTransferService\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_NetworkListManager\":[\"Win32_Networking\"],\"Win32_Networking_RemoteDifferentialCompression\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authentication_Identity_Provider\":[\"Win32_Security_Authentication_Identity\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Authorization_UI\":[\"Win32_Security_Authorization\"],\"Win32_Security_ConfigurationSnapin\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_Tpm\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DataDeduplication\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_EnhancedStorage\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileServerResourceManager\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_Packaging_Opc\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_VirtualDiskService\":[\"Win32_Storage\"],\"Win32_Storage_Vss\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_Storage_Xps_Printing\":[\"Win32_Storage_Xps\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_AssessmentTool\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_CallObj\":[\"Win32_System_Com\"],\"Win32_System_Com_ChannelCredentials\":[\"Win32_System_Com\"],\"Win32_System_Com_Events\":[\"Win32_System_Com\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_UI\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_Contacts\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DesktopSharing\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Mmc\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_ParentalControls\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_RealTimeCommunications\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteAssistance\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_ServerBackup\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SettingsManagementInfrastructure\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_TaskScheduler\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UpdateAgent\":[\"Win32_System\"],\"Win32_System_UpdateAssessment\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_WindowsSync\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_Animation\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_Controls_RichEdit\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Ink\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Radial\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_LegacyWindowsEnvironmentFeatures\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Notifications\":[\"Win32_UI\"],\"Win32_UI_Ribbon\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_UI_Wpf\":[\"Win32_UI\"],\"default\":[]}}", "windows-sys_0.48.0": "{\"dependencies\":[{\"name\":\"windows-targets\",\"req\":\"^0.48.0\"}],\"features\":{\"Wdk\":[],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Win32\":[],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Data_Xml\":[\"Win32_Data\"],\"Win32_Data_Xml_MsXml\":[\"Win32_Data_Xml\"],\"Win32_Data_Xml_XmlLite\":[\"Win32_Data_Xml\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAccess\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_FunctionDiscovery\":[\"Win32_Devices\"],\"Win32_Devices_Geolocation\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_ImageAcquisition\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_Audio_Apo\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectMusic\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_Endpoints\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_XAudio2\":[\"Win32_Media_Audio\"],\"Win32_Media_DeviceManager\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_LibrarySharingServices\":[\"Win32_Media\"],\"Win32_Media_MediaPlayer\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Speech\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_MobileBroadband\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkPolicyServer\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectNow\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_BackgroundIntelligentTransferService\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_NetworkListManager\":[\"Win32_Networking\"],\"Win32_Networking_RemoteDifferentialCompression\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authentication_Identity_Provider\":[\"Win32_Security_Authentication_Identity\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Authorization_UI\":[\"Win32_Security_Authorization\"],\"Win32_Security_ConfigurationSnapin\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_Tpm\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DataDeduplication\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_EnhancedStorage\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileServerResourceManager\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_Packaging_Opc\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_VirtualDiskService\":[\"Win32_Storage\"],\"Win32_Storage_Vss\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_Storage_Xps_Printing\":[\"Win32_Storage_Xps\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_AssessmentTool\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_CallObj\":[\"Win32_System_Com\"],\"Win32_System_Com_ChannelCredentials\":[\"Win32_System_Com\"],\"Win32_System_Com_Events\":[\"Win32_System_Com\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_UI\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_Contacts\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DesktopSharing\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ClrProfiling\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_ActiveScript\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Mmc\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_ParentalControls\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_RealTimeCommunications\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteAssistance\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_ServerBackup\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SettingsManagementInfrastructure\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_TaskScheduler\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UpdateAgent\":[\"Win32_System\"],\"Win32_System_UpdateAssessment\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_WindowsSync\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_Animation\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_Controls_RichEdit\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Ink\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Radial\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_LegacyWindowsEnvironmentFeatures\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Notifications\":[\"Win32_UI\"],\"Win32_UI_Ribbon\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_UI_Wpf\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[]}}", "windows-sys_0.52.0": "{\"dependencies\":[{\"name\":\"windows-targets\",\"req\":\"^0.52.0\"}],\"features\":{\"Wdk\":[],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Win32\":[],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[],\"docs\":[]}}", "windows-sys_0.59.0": "{\"dependencies\":[{\"name\":\"windows-targets\",\"req\":\"^0.52.6\"}],\"features\":{\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[],\"docs\":[]}}", "windows-sys_0.60.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-targets\",\"req\":\"^0.53.2\"}],\"features\":{\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_Beep\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Cdrom\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Dvd\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_Nfc\":[\"Win32_Devices\"],\"Win32_Devices_Nfp\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[],\"docs\":[]}}", - "windows-sys_0.61.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.0\"}],\"features\":{\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_Beep\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Cdrom\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Dvd\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_Nfc\":[\"Win32_Devices\"],\"Win32_Devices_Nfp\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[],\"docs\":[]}}", + "windows-sys_0.61.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"}],\"features\":{\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_Beep\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Cdrom\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Dvd\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_Nfc\":[\"Win32_Devices\"],\"Win32_Devices_Nfp\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[],\"docs\":[]}}", "windows-targets_0.42.2": "{\"dependencies\":[{\"name\":\"windows_aarch64_gnullvm\",\"req\":\"^0.42.2\",\"target\":\"aarch64-pc-windows-gnullvm\"},{\"name\":\"windows_aarch64_msvc\",\"req\":\"^0.42.2\",\"target\":\"aarch64-pc-windows-msvc\"},{\"name\":\"windows_aarch64_msvc\",\"req\":\"^0.42.2\",\"target\":\"aarch64-uwp-windows-msvc\"},{\"name\":\"windows_i686_gnu\",\"req\":\"^0.42.2\",\"target\":\"i686-pc-windows-gnu\"},{\"name\":\"windows_i686_gnu\",\"req\":\"^0.42.2\",\"target\":\"i686-uwp-windows-gnu\"},{\"name\":\"windows_i686_msvc\",\"req\":\"^0.42.2\",\"target\":\"i686-pc-windows-msvc\"},{\"name\":\"windows_i686_msvc\",\"req\":\"^0.42.2\",\"target\":\"i686-uwp-windows-msvc\"},{\"name\":\"windows_x86_64_gnu\",\"req\":\"^0.42.2\",\"target\":\"x86_64-pc-windows-gnu\"},{\"name\":\"windows_x86_64_gnu\",\"req\":\"^0.42.2\",\"target\":\"x86_64-uwp-windows-gnu\"},{\"name\":\"windows_x86_64_gnullvm\",\"req\":\"^0.42.2\",\"target\":\"x86_64-pc-windows-gnullvm\"},{\"name\":\"windows_x86_64_msvc\",\"req\":\"^0.42.2\",\"target\":\"x86_64-pc-windows-msvc\"},{\"name\":\"windows_x86_64_msvc\",\"req\":\"^0.42.2\",\"target\":\"x86_64-uwp-windows-msvc\"}],\"features\":{}}", "windows-targets_0.48.5": "{\"dependencies\":[{\"name\":\"windows_aarch64_gnullvm\",\"req\":\"^0.48.5\",\"target\":\"aarch64-pc-windows-gnullvm\"},{\"name\":\"windows_aarch64_msvc\",\"req\":\"^0.48.5\",\"target\":\"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnu\",\"req\":\"^0.48.5\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_msvc\",\"req\":\"^0.48.5\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnu\",\"req\":\"^0.48.5\",\"target\":\"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnullvm\",\"req\":\"^0.48.5\",\"target\":\"x86_64-pc-windows-gnullvm\"},{\"name\":\"windows_x86_64_msvc\",\"req\":\"^0.48.5\",\"target\":\"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"}],\"features\":{}}", "windows-targets_0.52.6": "{\"dependencies\":[{\"name\":\"windows_aarch64_gnullvm\",\"req\":\"^0.52.6\",\"target\":\"aarch64-pc-windows-gnullvm\"},{\"name\":\"windows_aarch64_msvc\",\"req\":\"^0.52.6\",\"target\":\"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnu\",\"req\":\"^0.52.6\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnullvm\",\"req\":\"^0.52.6\",\"target\":\"i686-pc-windows-gnullvm\"},{\"name\":\"windows_i686_msvc\",\"req\":\"^0.52.6\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnu\",\"req\":\"^0.52.6\",\"target\":\"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnullvm\",\"req\":\"^0.52.6\",\"target\":\"x86_64-pc-windows-gnullvm\"},{\"name\":\"windows_x86_64_msvc\",\"req\":\"^0.52.6\",\"target\":\"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"}],\"features\":{}}", - "windows-targets_0.53.2": "{\"dependencies\":[{\"name\":\"windows_aarch64_gnullvm\",\"req\":\"^0.53.0\",\"target\":\"aarch64-pc-windows-gnullvm\"},{\"name\":\"windows_aarch64_msvc\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnu\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnullvm\",\"req\":\"^0.53.0\",\"target\":\"i686-pc-windows-gnullvm\"},{\"name\":\"windows_i686_msvc\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnu\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnullvm\",\"req\":\"^0.53.0\",\"target\":\"x86_64-pc-windows-gnullvm\"},{\"name\":\"windows_x86_64_msvc\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"}],\"features\":{}}", - "windows-threading_0.1.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.1\"}],\"features\":{}}", + "windows-targets_0.53.5": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\",\"target\":\"cfg(windows_raw_dylib)\"},{\"name\":\"windows_aarch64_gnullvm\",\"req\":\"^0.53.0\",\"target\":\"aarch64-pc-windows-gnullvm\"},{\"name\":\"windows_aarch64_msvc\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"aarch64\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnu\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_i686_gnullvm\",\"req\":\"^0.53.0\",\"target\":\"i686-pc-windows-gnullvm\"},{\"name\":\"windows_i686_msvc\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"x86\\\", target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnu\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(target_arch = \\\"x86_64\\\", target_env = \\\"gnu\\\", not(target_abi = \\\"llvm\\\"), not(windows_raw_dylib)))\"},{\"name\":\"windows_x86_64_gnullvm\",\"req\":\"^0.53.0\",\"target\":\"x86_64-pc-windows-gnullvm\"},{\"name\":\"windows_x86_64_msvc\",\"req\":\"^0.53.0\",\"target\":\"cfg(all(any(target_arch = \\\"x86_64\\\", target_arch = \\\"arm64ec\\\"), target_env = \\\"msvc\\\", not(windows_raw_dylib)))\"}],\"features\":{}}", + "windows-threading_0.2.1": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.2.1\"}],\"features\":{}}", "windows_0.58.0": "{\"dependencies\":[{\"name\":\"windows-core\",\"req\":\"^0.58.0\"},{\"name\":\"windows-targets\",\"req\":\"^0.52.6\"}],\"features\":{\"AI\":[\"Foundation\"],\"AI_MachineLearning\":[\"AI\"],\"ApplicationModel\":[\"Foundation\"],\"ApplicationModel_Activation\":[\"ApplicationModel\"],\"ApplicationModel_AppExtensions\":[\"ApplicationModel\"],\"ApplicationModel_AppService\":[\"ApplicationModel\"],\"ApplicationModel_Appointments\":[\"ApplicationModel\"],\"ApplicationModel_Appointments_AppointmentsProvider\":[\"ApplicationModel_Appointments\"],\"ApplicationModel_Appointments_DataProvider\":[\"ApplicationModel_Appointments\"],\"ApplicationModel_Background\":[\"ApplicationModel\"],\"ApplicationModel_Calls\":[\"ApplicationModel\"],\"ApplicationModel_Calls_Background\":[\"ApplicationModel_Calls\"],\"ApplicationModel_Calls_Provider\":[\"ApplicationModel_Calls\"],\"ApplicationModel_Chat\":[\"ApplicationModel\"],\"ApplicationModel_CommunicationBlocking\":[\"ApplicationModel\"],\"ApplicationModel_Contacts\":[\"ApplicationModel\"],\"ApplicationModel_Contacts_DataProvider\":[\"ApplicationModel_Contacts\"],\"ApplicationModel_Contacts_Provider\":[\"ApplicationModel_Contacts\"],\"ApplicationModel_ConversationalAgent\":[\"ApplicationModel\"],\"ApplicationModel_Core\":[\"ApplicationModel\"],\"ApplicationModel_DataTransfer\":[\"ApplicationModel\"],\"ApplicationModel_DataTransfer_DragDrop\":[\"ApplicationModel_DataTransfer\"],\"ApplicationModel_DataTransfer_DragDrop_Core\":[\"ApplicationModel_DataTransfer_DragDrop\"],\"ApplicationModel_DataTransfer_ShareTarget\":[\"ApplicationModel_DataTransfer\"],\"ApplicationModel_Email\":[\"ApplicationModel\"],\"ApplicationModel_Email_DataProvider\":[\"ApplicationModel_Email\"],\"ApplicationModel_ExtendedExecution\":[\"ApplicationModel\"],\"ApplicationModel_ExtendedExecution_Foreground\":[\"ApplicationModel_ExtendedExecution\"],\"ApplicationModel_Holographic\":[\"ApplicationModel\"],\"ApplicationModel_LockScreen\":[\"ApplicationModel\"],\"ApplicationModel_PackageExtensions\":[\"ApplicationModel\"],\"ApplicationModel_Payments\":[\"ApplicationModel\"],\"ApplicationModel_Payments_Provider\":[\"ApplicationModel_Payments\"],\"ApplicationModel_Preview\":[\"ApplicationModel\"],\"ApplicationModel_Preview_Holographic\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Preview_InkWorkspace\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Preview_Notes\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Resources\":[\"ApplicationModel\"],\"ApplicationModel_Resources_Core\":[\"ApplicationModel_Resources\"],\"ApplicationModel_Resources_Management\":[\"ApplicationModel_Resources\"],\"ApplicationModel_Search\":[\"ApplicationModel\"],\"ApplicationModel_Search_Core\":[\"ApplicationModel_Search\"],\"ApplicationModel_UserActivities\":[\"ApplicationModel\"],\"ApplicationModel_UserActivities_Core\":[\"ApplicationModel_UserActivities\"],\"ApplicationModel_UserDataAccounts\":[\"ApplicationModel\"],\"ApplicationModel_UserDataAccounts_Provider\":[\"ApplicationModel_UserDataAccounts\"],\"ApplicationModel_UserDataAccounts_SystemAccess\":[\"ApplicationModel_UserDataAccounts\"],\"ApplicationModel_UserDataTasks\":[\"ApplicationModel\"],\"ApplicationModel_UserDataTasks_DataProvider\":[\"ApplicationModel_UserDataTasks\"],\"ApplicationModel_VoiceCommands\":[\"ApplicationModel\"],\"ApplicationModel_Wallet\":[\"ApplicationModel\"],\"ApplicationModel_Wallet_System\":[\"ApplicationModel_Wallet\"],\"Data\":[\"Foundation\"],\"Data_Html\":[\"Data\"],\"Data_Json\":[\"Data\"],\"Data_Pdf\":[\"Data\"],\"Data_Text\":[\"Data\"],\"Data_Xml\":[\"Data\"],\"Data_Xml_Dom\":[\"Data_Xml\"],\"Data_Xml_Xsl\":[\"Data_Xml\"],\"Devices\":[\"Foundation\"],\"Devices_Adc\":[\"Devices\"],\"Devices_Adc_Provider\":[\"Devices_Adc\"],\"Devices_Background\":[\"Devices\"],\"Devices_Bluetooth\":[\"Devices\"],\"Devices_Bluetooth_Advertisement\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_Background\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_GenericAttributeProfile\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_Rfcomm\":[\"Devices_Bluetooth\"],\"Devices_Custom\":[\"Devices\"],\"Devices_Display\":[\"Devices\"],\"Devices_Display_Core\":[\"Devices_Display\"],\"Devices_Enumeration\":[\"Devices\"],\"Devices_Enumeration_Pnp\":[\"Devices_Enumeration\"],\"Devices_Geolocation\":[\"Devices\"],\"Devices_Geolocation_Geofencing\":[\"Devices_Geolocation\"],\"Devices_Geolocation_Provider\":[\"Devices_Geolocation\"],\"Devices_Gpio\":[\"Devices\"],\"Devices_Gpio_Provider\":[\"Devices_Gpio\"],\"Devices_Haptics\":[\"Devices\"],\"Devices_HumanInterfaceDevice\":[\"Devices\"],\"Devices_I2c\":[\"Devices\"],\"Devices_I2c_Provider\":[\"Devices_I2c\"],\"Devices_Input\":[\"Devices\"],\"Devices_Input_Preview\":[\"Devices_Input\"],\"Devices_Lights\":[\"Devices\"],\"Devices_Lights_Effects\":[\"Devices_Lights\"],\"Devices_Midi\":[\"Devices\"],\"Devices_PointOfService\":[\"Devices\"],\"Devices_PointOfService_Provider\":[\"Devices_PointOfService\"],\"Devices_Portable\":[\"Devices\"],\"Devices_Power\":[\"Devices\"],\"Devices_Printers\":[\"Devices\"],\"Devices_Printers_Extensions\":[\"Devices_Printers\"],\"Devices_Pwm\":[\"Devices\"],\"Devices_Pwm_Provider\":[\"Devices_Pwm\"],\"Devices_Radios\":[\"Devices\"],\"Devices_Scanners\":[\"Devices\"],\"Devices_Sensors\":[\"Devices\"],\"Devices_Sensors_Custom\":[\"Devices_Sensors\"],\"Devices_SerialCommunication\":[\"Devices\"],\"Devices_SmartCards\":[\"Devices\"],\"Devices_Sms\":[\"Devices\"],\"Devices_Spi\":[\"Devices\"],\"Devices_Spi_Provider\":[\"Devices_Spi\"],\"Devices_Usb\":[\"Devices\"],\"Devices_WiFi\":[\"Devices\"],\"Devices_WiFiDirect\":[\"Devices\"],\"Devices_WiFiDirect_Services\":[\"Devices_WiFiDirect\"],\"Embedded\":[\"Foundation\"],\"Embedded_DeviceLockdown\":[\"Embedded\"],\"Foundation\":[],\"Foundation_Collections\":[\"Foundation\"],\"Foundation_Diagnostics\":[\"Foundation\"],\"Foundation_Metadata\":[\"Foundation\"],\"Foundation_Numerics\":[\"Foundation\"],\"Gaming\":[\"Foundation\"],\"Gaming_Input\":[\"Gaming\"],\"Gaming_Input_Custom\":[\"Gaming_Input\"],\"Gaming_Input_ForceFeedback\":[\"Gaming_Input\"],\"Gaming_Input_Preview\":[\"Gaming_Input\"],\"Gaming_Preview\":[\"Gaming\"],\"Gaming_Preview_GamesEnumeration\":[\"Gaming_Preview\"],\"Gaming_UI\":[\"Gaming\"],\"Gaming_XboxLive\":[\"Gaming\"],\"Gaming_XboxLive_Storage\":[\"Gaming_XboxLive\"],\"Globalization\":[\"Foundation\"],\"Globalization_Collation\":[\"Globalization\"],\"Globalization_DateTimeFormatting\":[\"Globalization\"],\"Globalization_Fonts\":[\"Globalization\"],\"Globalization_NumberFormatting\":[\"Globalization\"],\"Globalization_PhoneNumberFormatting\":[\"Globalization\"],\"Graphics\":[\"Foundation\"],\"Graphics_Capture\":[\"Graphics\"],\"Graphics_DirectX\":[\"Graphics\"],\"Graphics_DirectX_Direct3D11\":[\"Graphics_DirectX\"],\"Graphics_Display\":[\"Graphics\"],\"Graphics_Display_Core\":[\"Graphics_Display\"],\"Graphics_Effects\":[\"Graphics\"],\"Graphics_Holographic\":[\"Graphics\"],\"Graphics_Imaging\":[\"Graphics\"],\"Graphics_Printing\":[\"Graphics\"],\"Graphics_Printing3D\":[\"Graphics\"],\"Graphics_Printing_OptionDetails\":[\"Graphics_Printing\"],\"Graphics_Printing_PrintSupport\":[\"Graphics_Printing\"],\"Graphics_Printing_PrintTicket\":[\"Graphics_Printing\"],\"Graphics_Printing_Workflow\":[\"Graphics_Printing\"],\"Management\":[\"Foundation\"],\"Management_Core\":[\"Management\"],\"Management_Deployment\":[\"Management\"],\"Management_Deployment_Preview\":[\"Management_Deployment\"],\"Management_Policies\":[\"Management\"],\"Management_Setup\":[\"Management\"],\"Management_Update\":[\"Management\"],\"Management_Workplace\":[\"Management\"],\"Media\":[\"Foundation\"],\"Media_AppBroadcasting\":[\"Media\"],\"Media_AppRecording\":[\"Media\"],\"Media_Audio\":[\"Media\"],\"Media_Capture\":[\"Media\"],\"Media_Capture_Core\":[\"Media_Capture\"],\"Media_Capture_Frames\":[\"Media_Capture\"],\"Media_Casting\":[\"Media\"],\"Media_ClosedCaptioning\":[\"Media\"],\"Media_ContentRestrictions\":[\"Media\"],\"Media_Control\":[\"Media\"],\"Media_Core\":[\"Media\"],\"Media_Core_Preview\":[\"Media_Core\"],\"Media_Devices\":[\"Media\"],\"Media_Devices_Core\":[\"Media_Devices\"],\"Media_DialProtocol\":[\"Media\"],\"Media_Editing\":[\"Media\"],\"Media_Effects\":[\"Media\"],\"Media_FaceAnalysis\":[\"Media\"],\"Media_Import\":[\"Media\"],\"Media_MediaProperties\":[\"Media\"],\"Media_Miracast\":[\"Media\"],\"Media_Ocr\":[\"Media\"],\"Media_PlayTo\":[\"Media\"],\"Media_Playback\":[\"Media\"],\"Media_Playlists\":[\"Media\"],\"Media_Protection\":[\"Media\"],\"Media_Protection_PlayReady\":[\"Media_Protection\"],\"Media_Render\":[\"Media\"],\"Media_SpeechRecognition\":[\"Media\"],\"Media_SpeechSynthesis\":[\"Media\"],\"Media_Streaming\":[\"Media\"],\"Media_Streaming_Adaptive\":[\"Media_Streaming\"],\"Media_Transcoding\":[\"Media\"],\"Networking\":[\"Foundation\"],\"Networking_BackgroundTransfer\":[\"Networking\"],\"Networking_Connectivity\":[\"Networking\"],\"Networking_NetworkOperators\":[\"Networking\"],\"Networking_Proximity\":[\"Networking\"],\"Networking_PushNotifications\":[\"Networking\"],\"Networking_ServiceDiscovery\":[\"Networking\"],\"Networking_ServiceDiscovery_Dnssd\":[\"Networking_ServiceDiscovery\"],\"Networking_Sockets\":[\"Networking\"],\"Networking_Vpn\":[\"Networking\"],\"Networking_XboxLive\":[\"Networking\"],\"Perception\":[\"Foundation\"],\"Perception_Automation\":[\"Perception\"],\"Perception_Automation_Core\":[\"Perception_Automation\"],\"Perception_People\":[\"Perception\"],\"Perception_Spatial\":[\"Perception\"],\"Perception_Spatial_Preview\":[\"Perception_Spatial\"],\"Perception_Spatial_Surfaces\":[\"Perception_Spatial\"],\"Phone\":[\"Foundation\"],\"Phone_ApplicationModel\":[\"Phone\"],\"Phone_Devices\":[\"Phone\"],\"Phone_Devices_Notification\":[\"Phone_Devices\"],\"Phone_Devices_Power\":[\"Phone_Devices\"],\"Phone_Management\":[\"Phone\"],\"Phone_Management_Deployment\":[\"Phone_Management\"],\"Phone_Media\":[\"Phone\"],\"Phone_Media_Devices\":[\"Phone_Media\"],\"Phone_Notification\":[\"Phone\"],\"Phone_Notification_Management\":[\"Phone_Notification\"],\"Phone_PersonalInformation\":[\"Phone\"],\"Phone_PersonalInformation_Provisioning\":[\"Phone_PersonalInformation\"],\"Phone_Speech\":[\"Phone\"],\"Phone_Speech_Recognition\":[\"Phone_Speech\"],\"Phone_StartScreen\":[\"Phone\"],\"Phone_System\":[\"Phone\"],\"Phone_System_Power\":[\"Phone_System\"],\"Phone_System_Profile\":[\"Phone_System\"],\"Phone_System_UserProfile\":[\"Phone_System\"],\"Phone_System_UserProfile_GameServices\":[\"Phone_System_UserProfile\"],\"Phone_System_UserProfile_GameServices_Core\":[\"Phone_System_UserProfile_GameServices\"],\"Phone_UI\":[\"Phone\"],\"Phone_UI_Input\":[\"Phone_UI\"],\"Security\":[\"Foundation\"],\"Security_Authentication\":[\"Security\"],\"Security_Authentication_Identity\":[\"Security_Authentication\"],\"Security_Authentication_Identity_Core\":[\"Security_Authentication_Identity\"],\"Security_Authentication_OnlineId\":[\"Security_Authentication\"],\"Security_Authentication_Web\":[\"Security_Authentication\"],\"Security_Authentication_Web_Core\":[\"Security_Authentication_Web\"],\"Security_Authentication_Web_Provider\":[\"Security_Authentication_Web\"],\"Security_Authorization\":[\"Security\"],\"Security_Authorization_AppCapabilityAccess\":[\"Security_Authorization\"],\"Security_Credentials\":[\"Security\"],\"Security_Credentials_UI\":[\"Security_Credentials\"],\"Security_Cryptography\":[\"Security\"],\"Security_Cryptography_Certificates\":[\"Security_Cryptography\"],\"Security_Cryptography_Core\":[\"Security_Cryptography\"],\"Security_Cryptography_DataProtection\":[\"Security_Cryptography\"],\"Security_DataProtection\":[\"Security\"],\"Security_EnterpriseData\":[\"Security\"],\"Security_ExchangeActiveSyncProvisioning\":[\"Security\"],\"Security_Isolation\":[\"Security\"],\"Services\":[\"Foundation\"],\"Services_Maps\":[\"Services\"],\"Services_Maps_Guidance\":[\"Services_Maps\"],\"Services_Maps_LocalSearch\":[\"Services_Maps\"],\"Services_Maps_OfflineMaps\":[\"Services_Maps\"],\"Services_Store\":[\"Services\"],\"Services_TargetedContent\":[\"Services\"],\"Storage\":[\"Foundation\"],\"Storage_AccessCache\":[\"Storage\"],\"Storage_BulkAccess\":[\"Storage\"],\"Storage_Compression\":[\"Storage\"],\"Storage_FileProperties\":[\"Storage\"],\"Storage_Pickers\":[\"Storage\"],\"Storage_Pickers_Provider\":[\"Storage_Pickers\"],\"Storage_Provider\":[\"Storage\"],\"Storage_Search\":[\"Storage\"],\"Storage_Streams\":[\"Storage\"],\"System\":[\"Foundation\"],\"System_Diagnostics\":[\"System\"],\"System_Diagnostics_DevicePortal\":[\"System_Diagnostics\"],\"System_Diagnostics_Telemetry\":[\"System_Diagnostics\"],\"System_Diagnostics_TraceReporting\":[\"System_Diagnostics\"],\"System_Display\":[\"System\"],\"System_Implementation\":[\"System\"],\"System_Implementation_FileExplorer\":[\"System_Implementation\"],\"System_Inventory\":[\"System\"],\"System_Power\":[\"System\"],\"System_Profile\":[\"System\"],\"System_Profile_SystemManufacturers\":[\"System_Profile\"],\"System_RemoteDesktop\":[\"System\"],\"System_RemoteDesktop_Input\":[\"System_RemoteDesktop\"],\"System_RemoteDesktop_Provider\":[\"System_RemoteDesktop\"],\"System_RemoteSystems\":[\"System\"],\"System_Threading\":[\"System\"],\"System_Threading_Core\":[\"System_Threading\"],\"System_Update\":[\"System\"],\"System_UserProfile\":[\"System\"],\"UI\":[\"Foundation\"],\"UI_Accessibility\":[\"UI\"],\"UI_ApplicationSettings\":[\"UI\"],\"UI_Composition\":[\"UI\"],\"UI_Composition_Core\":[\"UI_Composition\"],\"UI_Composition_Desktop\":[\"UI_Composition\"],\"UI_Composition_Diagnostics\":[\"UI_Composition\"],\"UI_Composition_Effects\":[\"UI_Composition\"],\"UI_Composition_Interactions\":[\"UI_Composition\"],\"UI_Composition_Scenes\":[\"UI_Composition\"],\"UI_Core\":[\"UI\"],\"UI_Core_AnimationMetrics\":[\"UI_Core\"],\"UI_Core_Preview\":[\"UI_Core\"],\"UI_Input\":[\"UI\"],\"UI_Input_Core\":[\"UI_Input\"],\"UI_Input_Inking\":[\"UI_Input\"],\"UI_Input_Inking_Analysis\":[\"UI_Input_Inking\"],\"UI_Input_Inking_Core\":[\"UI_Input_Inking\"],\"UI_Input_Inking_Preview\":[\"UI_Input_Inking\"],\"UI_Input_Preview\":[\"UI_Input\"],\"UI_Input_Preview_Injection\":[\"UI_Input_Preview\"],\"UI_Input_Spatial\":[\"UI_Input\"],\"UI_Notifications\":[\"UI\"],\"UI_Notifications_Management\":[\"UI_Notifications\"],\"UI_Notifications_Preview\":[\"UI_Notifications\"],\"UI_Popups\":[\"UI\"],\"UI_Shell\":[\"UI\"],\"UI_StartScreen\":[\"UI\"],\"UI_Text\":[\"UI\"],\"UI_Text_Core\":[\"UI_Text\"],\"UI_UIAutomation\":[\"UI\"],\"UI_UIAutomation_Core\":[\"UI_UIAutomation\"],\"UI_ViewManagement\":[\"UI\"],\"UI_ViewManagement_Core\":[\"UI_ViewManagement\"],\"UI_WebUI\":[\"UI\"],\"UI_WebUI_Core\":[\"UI_WebUI\"],\"UI_WindowManagement\":[\"UI\"],\"UI_WindowManagement_Preview\":[\"UI_WindowManagement\"],\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Web\":[\"Foundation\"],\"Web_AtomPub\":[\"Web\"],\"Web_Http\":[\"Web\"],\"Web_Http_Diagnostics\":[\"Web_Http\"],\"Web_Http_Filters\":[\"Web_Http\"],\"Web_Http_Headers\":[\"Web_Http\"],\"Web_Syndication\":[\"Web\"],\"Web_UI\":[\"Web\"],\"Web_UI_Interop\":[\"Web_UI\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_AI\":[\"Win32\"],\"Win32_AI_MachineLearning\":[\"Win32_AI\"],\"Win32_AI_MachineLearning_DirectML\":[\"Win32_AI_MachineLearning\"],\"Win32_AI_MachineLearning_WinML\":[\"Win32_AI_MachineLearning\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Data_Xml\":[\"Win32_Data\"],\"Win32_Data_Xml_MsXml\":[\"Win32_Data_Xml\"],\"Win32_Data_Xml_XmlLite\":[\"Win32_Data_Xml\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAccess\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_FunctionDiscovery\":[\"Win32_Devices\"],\"Win32_Devices_Geolocation\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_ImageAcquisition\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_CompositionSwapchain\":[\"Win32_Graphics\"],\"Win32_Graphics_DXCore\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct2D\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct2D_Common\":[\"Win32_Graphics_Direct2D\"],\"Win32_Graphics_Direct3D\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D10\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D11\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D11on12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D9\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D9on12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D_Dxc\":[\"Win32_Graphics_Direct3D\"],\"Win32_Graphics_Direct3D_Fxc\":[\"Win32_Graphics_Direct3D\"],\"Win32_Graphics_DirectComposition\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectDraw\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectManipulation\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectWrite\":[\"Win32_Graphics\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Dxgi\":[\"Win32_Graphics\"],\"Win32_Graphics_Dxgi_Common\":[\"Win32_Graphics_Dxgi\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_Imaging\":[\"Win32_Graphics\"],\"Win32_Graphics_Imaging_D2D\":[\"Win32_Graphics_Imaging\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_Audio_Apo\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectMusic\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectSound\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_Endpoints\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_XAudio2\":[\"Win32_Media_Audio\"],\"Win32_Media_DeviceManager\":[\"Win32_Media\"],\"Win32_Media_DirectShow\":[\"Win32_Media\"],\"Win32_Media_DirectShow_Tv\":[\"Win32_Media_DirectShow\"],\"Win32_Media_DirectShow_Xml\":[\"Win32_Media_DirectShow\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_LibrarySharingServices\":[\"Win32_Media\"],\"Win32_Media_MediaFoundation\":[\"Win32_Media\"],\"Win32_Media_MediaPlayer\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_PictureAcquisition\":[\"Win32_Media\"],\"Win32_Media_Speech\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_MobileBroadband\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkPolicyServer\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectNow\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_BackgroundIntelligentTransferService\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_NetworkListManager\":[\"Win32_Networking\"],\"Win32_Networking_RemoteDifferentialCompression\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authentication_Identity_Provider\":[\"Win32_Security_Authentication_Identity\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Authorization_UI\":[\"Win32_Security_Authorization\"],\"Win32_Security_ConfigurationSnapin\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_Tpm\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DataDeduplication\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_EnhancedStorage\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileServerResourceManager\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_Packaging_Opc\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_VirtualDiskService\":[\"Win32_Storage\"],\"Win32_Storage_Vss\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_Storage_Xps_Printing\":[\"Win32_Storage_Xps\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_AssessmentTool\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_CallObj\":[\"Win32_System_Com\"],\"Win32_System_Com_ChannelCredentials\":[\"Win32_System_Com\"],\"Win32_System_Com_Events\":[\"Win32_System_Com\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_UI\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_Contacts\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DesktopSharing\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ClrProfiling\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_ActiveScript\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Mmc\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_ParentalControls\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_RealTimeCommunications\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteAssistance\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_ServerBackup\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SettingsManagementInfrastructure\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_SideShow\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_TaskScheduler\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_TransactionServer\":[\"Win32_System\"],\"Win32_System_UpdateAgent\":[\"Win32_System\"],\"Win32_System_UpdateAssessment\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WinRT\":[\"Win32_System\"],\"Win32_System_WinRT_AllJoyn\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Composition\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_CoreInputView\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Direct3D11\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Display\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Graphics\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Graphics_Capture\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Graphics_Direct2D\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Graphics_Imaging\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Holographic\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Isolation\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_ML\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Media\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Metadata\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Pdf\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Printing\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Shell\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Storage\":[\"Win32_System_WinRT\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_WindowsSync\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_Animation\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_Controls_RichEdit\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Ink\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Radial\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_LegacyWindowsEnvironmentFeatures\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Notifications\":[\"Win32_UI\"],\"Win32_UI_Ribbon\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_UI_Wpf\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[\"std\"],\"deprecated\":[],\"docs\":[],\"implement\":[],\"std\":[\"windows-core/std\"]}}", - "windows_0.61.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-collections\",\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.61.2\"},{\"default_features\":false,\"name\":\"windows-future\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"windows-link\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"windows-numerics\",\"req\":\"^0.2.0\"}],\"features\":{\"AI\":[\"Foundation\"],\"AI_MachineLearning\":[\"AI\"],\"ApplicationModel\":[\"Foundation\"],\"ApplicationModel_Activation\":[\"ApplicationModel\"],\"ApplicationModel_AppExtensions\":[\"ApplicationModel\"],\"ApplicationModel_AppService\":[\"ApplicationModel\"],\"ApplicationModel_Appointments\":[\"ApplicationModel\"],\"ApplicationModel_Appointments_AppointmentsProvider\":[\"ApplicationModel_Appointments\"],\"ApplicationModel_Appointments_DataProvider\":[\"ApplicationModel_Appointments\"],\"ApplicationModel_Background\":[\"ApplicationModel\"],\"ApplicationModel_Calls\":[\"ApplicationModel\"],\"ApplicationModel_Calls_Background\":[\"ApplicationModel_Calls\"],\"ApplicationModel_Calls_Provider\":[\"ApplicationModel_Calls\"],\"ApplicationModel_Chat\":[\"ApplicationModel\"],\"ApplicationModel_CommunicationBlocking\":[\"ApplicationModel\"],\"ApplicationModel_Contacts\":[\"ApplicationModel\"],\"ApplicationModel_Contacts_DataProvider\":[\"ApplicationModel_Contacts\"],\"ApplicationModel_Contacts_Provider\":[\"ApplicationModel_Contacts\"],\"ApplicationModel_ConversationalAgent\":[\"ApplicationModel\"],\"ApplicationModel_Core\":[\"ApplicationModel\"],\"ApplicationModel_DataTransfer\":[\"ApplicationModel\"],\"ApplicationModel_DataTransfer_DragDrop\":[\"ApplicationModel_DataTransfer\"],\"ApplicationModel_DataTransfer_DragDrop_Core\":[\"ApplicationModel_DataTransfer_DragDrop\"],\"ApplicationModel_DataTransfer_ShareTarget\":[\"ApplicationModel_DataTransfer\"],\"ApplicationModel_Email\":[\"ApplicationModel\"],\"ApplicationModel_Email_DataProvider\":[\"ApplicationModel_Email\"],\"ApplicationModel_ExtendedExecution\":[\"ApplicationModel\"],\"ApplicationModel_ExtendedExecution_Foreground\":[\"ApplicationModel_ExtendedExecution\"],\"ApplicationModel_Holographic\":[\"ApplicationModel\"],\"ApplicationModel_LockScreen\":[\"ApplicationModel\"],\"ApplicationModel_PackageExtensions\":[\"ApplicationModel\"],\"ApplicationModel_Payments\":[\"ApplicationModel\"],\"ApplicationModel_Payments_Provider\":[\"ApplicationModel_Payments\"],\"ApplicationModel_Preview\":[\"ApplicationModel\"],\"ApplicationModel_Preview_Holographic\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Preview_InkWorkspace\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Preview_Notes\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Resources\":[\"ApplicationModel\"],\"ApplicationModel_Resources_Core\":[\"ApplicationModel_Resources\"],\"ApplicationModel_Resources_Management\":[\"ApplicationModel_Resources\"],\"ApplicationModel_Search\":[\"ApplicationModel\"],\"ApplicationModel_Search_Core\":[\"ApplicationModel_Search\"],\"ApplicationModel_UserActivities\":[\"ApplicationModel\"],\"ApplicationModel_UserActivities_Core\":[\"ApplicationModel_UserActivities\"],\"ApplicationModel_UserDataAccounts\":[\"ApplicationModel\"],\"ApplicationModel_UserDataAccounts_Provider\":[\"ApplicationModel_UserDataAccounts\"],\"ApplicationModel_UserDataAccounts_SystemAccess\":[\"ApplicationModel_UserDataAccounts\"],\"ApplicationModel_UserDataTasks\":[\"ApplicationModel\"],\"ApplicationModel_UserDataTasks_DataProvider\":[\"ApplicationModel_UserDataTasks\"],\"ApplicationModel_VoiceCommands\":[\"ApplicationModel\"],\"ApplicationModel_Wallet\":[\"ApplicationModel\"],\"ApplicationModel_Wallet_System\":[\"ApplicationModel_Wallet\"],\"Data\":[\"Foundation\"],\"Data_Html\":[\"Data\"],\"Data_Json\":[\"Data\"],\"Data_Pdf\":[\"Data\"],\"Data_Text\":[\"Data\"],\"Data_Xml\":[\"Data\"],\"Data_Xml_Dom\":[\"Data_Xml\"],\"Data_Xml_Xsl\":[\"Data_Xml\"],\"Devices\":[\"Foundation\"],\"Devices_Adc\":[\"Devices\"],\"Devices_Adc_Provider\":[\"Devices_Adc\"],\"Devices_Background\":[\"Devices\"],\"Devices_Bluetooth\":[\"Devices\"],\"Devices_Bluetooth_Advertisement\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_Background\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_GenericAttributeProfile\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_Rfcomm\":[\"Devices_Bluetooth\"],\"Devices_Custom\":[\"Devices\"],\"Devices_Display\":[\"Devices\"],\"Devices_Display_Core\":[\"Devices_Display\"],\"Devices_Enumeration\":[\"Devices\"],\"Devices_Enumeration_Pnp\":[\"Devices_Enumeration\"],\"Devices_Geolocation\":[\"Devices\"],\"Devices_Geolocation_Geofencing\":[\"Devices_Geolocation\"],\"Devices_Geolocation_Provider\":[\"Devices_Geolocation\"],\"Devices_Gpio\":[\"Devices\"],\"Devices_Gpio_Provider\":[\"Devices_Gpio\"],\"Devices_Haptics\":[\"Devices\"],\"Devices_HumanInterfaceDevice\":[\"Devices\"],\"Devices_I2c\":[\"Devices\"],\"Devices_I2c_Provider\":[\"Devices_I2c\"],\"Devices_Input\":[\"Devices\"],\"Devices_Input_Preview\":[\"Devices_Input\"],\"Devices_Lights\":[\"Devices\"],\"Devices_Lights_Effects\":[\"Devices_Lights\"],\"Devices_Midi\":[\"Devices\"],\"Devices_PointOfService\":[\"Devices\"],\"Devices_PointOfService_Provider\":[\"Devices_PointOfService\"],\"Devices_Portable\":[\"Devices\"],\"Devices_Power\":[\"Devices\"],\"Devices_Printers\":[\"Devices\"],\"Devices_Printers_Extensions\":[\"Devices_Printers\"],\"Devices_Pwm\":[\"Devices\"],\"Devices_Pwm_Provider\":[\"Devices_Pwm\"],\"Devices_Radios\":[\"Devices\"],\"Devices_Scanners\":[\"Devices\"],\"Devices_Sensors\":[\"Devices\"],\"Devices_Sensors_Custom\":[\"Devices_Sensors\"],\"Devices_SerialCommunication\":[\"Devices\"],\"Devices_SmartCards\":[\"Devices\"],\"Devices_Sms\":[\"Devices\"],\"Devices_Spi\":[\"Devices\"],\"Devices_Spi_Provider\":[\"Devices_Spi\"],\"Devices_Usb\":[\"Devices\"],\"Devices_WiFi\":[\"Devices\"],\"Devices_WiFiDirect\":[\"Devices\"],\"Devices_WiFiDirect_Services\":[\"Devices_WiFiDirect\"],\"Embedded\":[\"Foundation\"],\"Embedded_DeviceLockdown\":[\"Embedded\"],\"Foundation\":[],\"Foundation_Collections\":[\"Foundation\"],\"Foundation_Diagnostics\":[\"Foundation\"],\"Foundation_Metadata\":[\"Foundation\"],\"Foundation_Numerics\":[\"Foundation\"],\"Gaming\":[\"Foundation\"],\"Gaming_Input\":[\"Gaming\"],\"Gaming_Input_Custom\":[\"Gaming_Input\"],\"Gaming_Input_ForceFeedback\":[\"Gaming_Input\"],\"Gaming_Input_Preview\":[\"Gaming_Input\"],\"Gaming_Preview\":[\"Gaming\"],\"Gaming_Preview_GamesEnumeration\":[\"Gaming_Preview\"],\"Gaming_UI\":[\"Gaming\"],\"Gaming_XboxLive\":[\"Gaming\"],\"Gaming_XboxLive_Storage\":[\"Gaming_XboxLive\"],\"Globalization\":[\"Foundation\"],\"Globalization_Collation\":[\"Globalization\"],\"Globalization_DateTimeFormatting\":[\"Globalization\"],\"Globalization_Fonts\":[\"Globalization\"],\"Globalization_NumberFormatting\":[\"Globalization\"],\"Globalization_PhoneNumberFormatting\":[\"Globalization\"],\"Graphics\":[\"Foundation\"],\"Graphics_Capture\":[\"Graphics\"],\"Graphics_DirectX\":[\"Graphics\"],\"Graphics_DirectX_Direct3D11\":[\"Graphics_DirectX\"],\"Graphics_Display\":[\"Graphics\"],\"Graphics_Display_Core\":[\"Graphics_Display\"],\"Graphics_Effects\":[\"Graphics\"],\"Graphics_Holographic\":[\"Graphics\"],\"Graphics_Imaging\":[\"Graphics\"],\"Graphics_Printing\":[\"Graphics\"],\"Graphics_Printing3D\":[\"Graphics\"],\"Graphics_Printing_OptionDetails\":[\"Graphics_Printing\"],\"Graphics_Printing_PrintSupport\":[\"Graphics_Printing\"],\"Graphics_Printing_PrintTicket\":[\"Graphics_Printing\"],\"Graphics_Printing_Workflow\":[\"Graphics_Printing\"],\"Management\":[\"Foundation\"],\"Management_Core\":[\"Management\"],\"Management_Deployment\":[\"Management\"],\"Management_Deployment_Preview\":[\"Management_Deployment\"],\"Management_Policies\":[\"Management\"],\"Management_Setup\":[\"Management\"],\"Management_Update\":[\"Management\"],\"Management_Workplace\":[\"Management\"],\"Media\":[\"Foundation\"],\"Media_AppBroadcasting\":[\"Media\"],\"Media_AppRecording\":[\"Media\"],\"Media_Audio\":[\"Media\"],\"Media_Capture\":[\"Media\"],\"Media_Capture_Core\":[\"Media_Capture\"],\"Media_Capture_Frames\":[\"Media_Capture\"],\"Media_Casting\":[\"Media\"],\"Media_ClosedCaptioning\":[\"Media\"],\"Media_ContentRestrictions\":[\"Media\"],\"Media_Control\":[\"Media\"],\"Media_Core\":[\"Media\"],\"Media_Core_Preview\":[\"Media_Core\"],\"Media_Devices\":[\"Media\"],\"Media_Devices_Core\":[\"Media_Devices\"],\"Media_DialProtocol\":[\"Media\"],\"Media_Editing\":[\"Media\"],\"Media_Effects\":[\"Media\"],\"Media_FaceAnalysis\":[\"Media\"],\"Media_Import\":[\"Media\"],\"Media_MediaProperties\":[\"Media\"],\"Media_Miracast\":[\"Media\"],\"Media_Ocr\":[\"Media\"],\"Media_PlayTo\":[\"Media\"],\"Media_Playback\":[\"Media\"],\"Media_Playlists\":[\"Media\"],\"Media_Protection\":[\"Media\"],\"Media_Protection_PlayReady\":[\"Media_Protection\"],\"Media_Render\":[\"Media\"],\"Media_SpeechRecognition\":[\"Media\"],\"Media_SpeechSynthesis\":[\"Media\"],\"Media_Streaming\":[\"Media\"],\"Media_Streaming_Adaptive\":[\"Media_Streaming\"],\"Media_Transcoding\":[\"Media\"],\"Networking\":[\"Foundation\"],\"Networking_BackgroundTransfer\":[\"Networking\"],\"Networking_Connectivity\":[\"Networking\"],\"Networking_NetworkOperators\":[\"Networking\"],\"Networking_Proximity\":[\"Networking\"],\"Networking_PushNotifications\":[\"Networking\"],\"Networking_ServiceDiscovery\":[\"Networking\"],\"Networking_ServiceDiscovery_Dnssd\":[\"Networking_ServiceDiscovery\"],\"Networking_Sockets\":[\"Networking\"],\"Networking_Vpn\":[\"Networking\"],\"Networking_XboxLive\":[\"Networking\"],\"Perception\":[\"Foundation\"],\"Perception_Automation\":[\"Perception\"],\"Perception_Automation_Core\":[\"Perception_Automation\"],\"Perception_People\":[\"Perception\"],\"Perception_Spatial\":[\"Perception\"],\"Perception_Spatial_Preview\":[\"Perception_Spatial\"],\"Perception_Spatial_Surfaces\":[\"Perception_Spatial\"],\"Phone\":[\"Foundation\"],\"Phone_ApplicationModel\":[\"Phone\"],\"Phone_Devices\":[\"Phone\"],\"Phone_Devices_Notification\":[\"Phone_Devices\"],\"Phone_Devices_Power\":[\"Phone_Devices\"],\"Phone_Management\":[\"Phone\"],\"Phone_Management_Deployment\":[\"Phone_Management\"],\"Phone_Media\":[\"Phone\"],\"Phone_Media_Devices\":[\"Phone_Media\"],\"Phone_Notification\":[\"Phone\"],\"Phone_Notification_Management\":[\"Phone_Notification\"],\"Phone_PersonalInformation\":[\"Phone\"],\"Phone_PersonalInformation_Provisioning\":[\"Phone_PersonalInformation\"],\"Phone_Speech\":[\"Phone\"],\"Phone_Speech_Recognition\":[\"Phone_Speech\"],\"Phone_StartScreen\":[\"Phone\"],\"Phone_System\":[\"Phone\"],\"Phone_System_Power\":[\"Phone_System\"],\"Phone_System_Profile\":[\"Phone_System\"],\"Phone_System_UserProfile\":[\"Phone_System\"],\"Phone_System_UserProfile_GameServices\":[\"Phone_System_UserProfile\"],\"Phone_System_UserProfile_GameServices_Core\":[\"Phone_System_UserProfile_GameServices\"],\"Phone_UI\":[\"Phone\"],\"Phone_UI_Input\":[\"Phone_UI\"],\"Security\":[\"Foundation\"],\"Security_Authentication\":[\"Security\"],\"Security_Authentication_Identity\":[\"Security_Authentication\"],\"Security_Authentication_Identity_Core\":[\"Security_Authentication_Identity\"],\"Security_Authentication_OnlineId\":[\"Security_Authentication\"],\"Security_Authentication_Web\":[\"Security_Authentication\"],\"Security_Authentication_Web_Core\":[\"Security_Authentication_Web\"],\"Security_Authentication_Web_Provider\":[\"Security_Authentication_Web\"],\"Security_Authorization\":[\"Security\"],\"Security_Authorization_AppCapabilityAccess\":[\"Security_Authorization\"],\"Security_Credentials\":[\"Security\"],\"Security_Credentials_UI\":[\"Security_Credentials\"],\"Security_Cryptography\":[\"Security\"],\"Security_Cryptography_Certificates\":[\"Security_Cryptography\"],\"Security_Cryptography_Core\":[\"Security_Cryptography\"],\"Security_Cryptography_DataProtection\":[\"Security_Cryptography\"],\"Security_DataProtection\":[\"Security\"],\"Security_EnterpriseData\":[\"Security\"],\"Security_ExchangeActiveSyncProvisioning\":[\"Security\"],\"Security_Isolation\":[\"Security\"],\"Services\":[\"Foundation\"],\"Services_Maps\":[\"Services\"],\"Services_Maps_Guidance\":[\"Services_Maps\"],\"Services_Maps_LocalSearch\":[\"Services_Maps\"],\"Services_Maps_OfflineMaps\":[\"Services_Maps\"],\"Services_Store\":[\"Services\"],\"Services_TargetedContent\":[\"Services\"],\"Storage\":[\"Foundation\"],\"Storage_AccessCache\":[\"Storage\"],\"Storage_BulkAccess\":[\"Storage\"],\"Storage_Compression\":[\"Storage\"],\"Storage_FileProperties\":[\"Storage\"],\"Storage_Pickers\":[\"Storage\"],\"Storage_Pickers_Provider\":[\"Storage_Pickers\"],\"Storage_Provider\":[\"Storage\"],\"Storage_Search\":[\"Storage\"],\"Storage_Streams\":[\"Storage\"],\"System\":[\"Foundation\"],\"System_Diagnostics\":[\"System\"],\"System_Diagnostics_DevicePortal\":[\"System_Diagnostics\"],\"System_Diagnostics_Telemetry\":[\"System_Diagnostics\"],\"System_Diagnostics_TraceReporting\":[\"System_Diagnostics\"],\"System_Display\":[\"System\"],\"System_Implementation\":[\"System\"],\"System_Implementation_FileExplorer\":[\"System_Implementation\"],\"System_Inventory\":[\"System\"],\"System_Power\":[\"System\"],\"System_Profile\":[\"System\"],\"System_Profile_SystemManufacturers\":[\"System_Profile\"],\"System_RemoteDesktop\":[\"System\"],\"System_RemoteDesktop_Input\":[\"System_RemoteDesktop\"],\"System_RemoteDesktop_Provider\":[\"System_RemoteDesktop\"],\"System_RemoteSystems\":[\"System\"],\"System_Threading\":[\"System\"],\"System_Threading_Core\":[\"System_Threading\"],\"System_Update\":[\"System\"],\"System_UserProfile\":[\"System\"],\"UI\":[\"Foundation\"],\"UI_Accessibility\":[\"UI\"],\"UI_ApplicationSettings\":[\"UI\"],\"UI_Composition\":[\"UI\"],\"UI_Composition_Core\":[\"UI_Composition\"],\"UI_Composition_Desktop\":[\"UI_Composition\"],\"UI_Composition_Diagnostics\":[\"UI_Composition\"],\"UI_Composition_Effects\":[\"UI_Composition\"],\"UI_Composition_Interactions\":[\"UI_Composition\"],\"UI_Composition_Scenes\":[\"UI_Composition\"],\"UI_Core\":[\"UI\"],\"UI_Core_AnimationMetrics\":[\"UI_Core\"],\"UI_Core_Preview\":[\"UI_Core\"],\"UI_Input\":[\"UI\"],\"UI_Input_Core\":[\"UI_Input\"],\"UI_Input_Inking\":[\"UI_Input\"],\"UI_Input_Inking_Analysis\":[\"UI_Input_Inking\"],\"UI_Input_Inking_Core\":[\"UI_Input_Inking\"],\"UI_Input_Inking_Preview\":[\"UI_Input_Inking\"],\"UI_Input_Preview\":[\"UI_Input\"],\"UI_Input_Preview_Injection\":[\"UI_Input_Preview\"],\"UI_Input_Spatial\":[\"UI_Input\"],\"UI_Notifications\":[\"UI\"],\"UI_Notifications_Management\":[\"UI_Notifications\"],\"UI_Notifications_Preview\":[\"UI_Notifications\"],\"UI_Popups\":[\"UI\"],\"UI_Shell\":[\"UI\"],\"UI_StartScreen\":[\"UI\"],\"UI_Text\":[\"UI\"],\"UI_Text_Core\":[\"UI_Text\"],\"UI_UIAutomation\":[\"UI\"],\"UI_UIAutomation_Core\":[\"UI_UIAutomation\"],\"UI_ViewManagement\":[\"UI\"],\"UI_ViewManagement_Core\":[\"UI_ViewManagement\"],\"UI_WebUI\":[\"UI\"],\"UI_WebUI_Core\":[\"UI_WebUI\"],\"UI_WindowManagement\":[\"UI\"],\"UI_WindowManagement_Preview\":[\"UI_WindowManagement\"],\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Web\":[\"Foundation\"],\"Web_AtomPub\":[\"Web\"],\"Web_Http\":[\"Web\"],\"Web_Http_Diagnostics\":[\"Web_Http\"],\"Web_Http_Filters\":[\"Web_Http\"],\"Web_Http_Headers\":[\"Web_Http\"],\"Web_Syndication\":[\"Web\"],\"Web_UI\":[\"Web\"],\"Web_UI_Interop\":[\"Web_UI\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_AI\":[\"Win32\"],\"Win32_AI_MachineLearning\":[\"Win32_AI\"],\"Win32_AI_MachineLearning_DirectML\":[\"Win32_AI_MachineLearning\"],\"Win32_AI_MachineLearning_WinML\":[\"Win32_AI_MachineLearning\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Data_Xml\":[\"Win32_Data\"],\"Win32_Data_Xml_MsXml\":[\"Win32_Data_Xml\"],\"Win32_Data_Xml_XmlLite\":[\"Win32_Data_Xml\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_Beep\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Cdrom\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAccess\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Dvd\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_FunctionDiscovery\":[\"Win32_Devices\"],\"Win32_Devices_Geolocation\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_ImageAcquisition\":[\"Win32_Devices\"],\"Win32_Devices_Nfc\":[\"Win32_Devices\"],\"Win32_Devices_Nfp\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_CompositionSwapchain\":[\"Win32_Graphics\"],\"Win32_Graphics_DXCore\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct2D\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct2D_Common\":[\"Win32_Graphics_Direct2D\"],\"Win32_Graphics_Direct3D\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D10\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D11\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D11on12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D9\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D9on12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D_Dxc\":[\"Win32_Graphics_Direct3D\"],\"Win32_Graphics_Direct3D_Fxc\":[\"Win32_Graphics_Direct3D\"],\"Win32_Graphics_DirectComposition\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectDraw\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectManipulation\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectWrite\":[\"Win32_Graphics\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Dxgi\":[\"Win32_Graphics\"],\"Win32_Graphics_Dxgi_Common\":[\"Win32_Graphics_Dxgi\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_Imaging\":[\"Win32_Graphics\"],\"Win32_Graphics_Imaging_D2D\":[\"Win32_Graphics_Imaging\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_Audio_Apo\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectMusic\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectSound\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_Endpoints\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_XAudio2\":[\"Win32_Media_Audio\"],\"Win32_Media_DeviceManager\":[\"Win32_Media\"],\"Win32_Media_DirectShow\":[\"Win32_Media\"],\"Win32_Media_DirectShow_Tv\":[\"Win32_Media_DirectShow\"],\"Win32_Media_DirectShow_Xml\":[\"Win32_Media_DirectShow\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_LibrarySharingServices\":[\"Win32_Media\"],\"Win32_Media_MediaFoundation\":[\"Win32_Media\"],\"Win32_Media_MediaPlayer\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_PictureAcquisition\":[\"Win32_Media\"],\"Win32_Media_Speech\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_MobileBroadband\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkPolicyServer\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectNow\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_BackgroundIntelligentTransferService\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_NetworkListManager\":[\"Win32_Networking\"],\"Win32_Networking_RemoteDifferentialCompression\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authentication_Identity_Provider\":[\"Win32_Security_Authentication_Identity\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Authorization_UI\":[\"Win32_Security_Authorization\"],\"Win32_Security_ConfigurationSnapin\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_Tpm\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DataDeduplication\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_EnhancedStorage\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileServerResourceManager\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_Packaging_Opc\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_VirtualDiskService\":[\"Win32_Storage\"],\"Win32_Storage_Vss\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_Storage_Xps_Printing\":[\"Win32_Storage_Xps\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_AssessmentTool\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_CallObj\":[\"Win32_System_Com\"],\"Win32_System_Com_ChannelCredentials\":[\"Win32_System_Com\"],\"Win32_System_Com_Events\":[\"Win32_System_Com\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_UI\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_Contacts\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DesktopSharing\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ClrProfiling\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_ActiveScript\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Mmc\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_ParentalControls\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_RealTimeCommunications\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteAssistance\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_ServerBackup\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SettingsManagementInfrastructure\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_SideShow\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_TaskScheduler\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_TransactionServer\":[\"Win32_System\"],\"Win32_System_UpdateAgent\":[\"Win32_System\"],\"Win32_System_UpdateAssessment\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WinRT\":[\"Win32_System\"],\"Win32_System_WinRT_AllJoyn\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Composition\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_CoreInputView\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Direct3D11\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Display\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Graphics\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Graphics_Capture\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Graphics_Direct2D\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Graphics_Imaging\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Holographic\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Isolation\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_ML\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Media\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Metadata\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Pdf\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Printing\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Shell\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Storage\":[\"Win32_System_WinRT\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_WindowsSync\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_Animation\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_Controls_RichEdit\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Ink\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Radial\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_LegacyWindowsEnvironmentFeatures\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Notifications\":[\"Win32_UI\"],\"Win32_UI_Ribbon\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_UI_Wpf\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[\"std\"],\"deprecated\":[],\"docs\":[],\"std\":[\"windows-core/std\"]}}", + "windows_0.62.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"windows-collections\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"name\":\"windows-core\",\"req\":\"^0.62.2\"},{\"default_features\":false,\"name\":\"windows-future\",\"req\":\"^0.3.2\"},{\"default_features\":false,\"name\":\"windows-numerics\",\"req\":\"^0.3.1\"}],\"features\":{\"AI\":[\"Foundation\"],\"AI_Actions\":[\"AI\"],\"AI_Actions_Hosting\":[\"AI_Actions\"],\"AI_Actions_Provider\":[\"AI_Actions\"],\"AI_Agents\":[\"AI\"],\"AI_Agents_Mcp\":[\"AI_Agents\"],\"AI_MachineLearning\":[\"AI\"],\"ApplicationModel\":[\"Foundation\"],\"ApplicationModel_Activation\":[\"ApplicationModel\"],\"ApplicationModel_AppExtensions\":[\"ApplicationModel\"],\"ApplicationModel_AppService\":[\"ApplicationModel\"],\"ApplicationModel_Appointments\":[\"ApplicationModel\"],\"ApplicationModel_Appointments_AppointmentsProvider\":[\"ApplicationModel_Appointments\"],\"ApplicationModel_Appointments_DataProvider\":[\"ApplicationModel_Appointments\"],\"ApplicationModel_Background\":[\"ApplicationModel\"],\"ApplicationModel_Calls\":[\"ApplicationModel\"],\"ApplicationModel_Calls_Background\":[\"ApplicationModel_Calls\"],\"ApplicationModel_Calls_Provider\":[\"ApplicationModel_Calls\"],\"ApplicationModel_Chat\":[\"ApplicationModel\"],\"ApplicationModel_CommunicationBlocking\":[\"ApplicationModel\"],\"ApplicationModel_Contacts\":[\"ApplicationModel\"],\"ApplicationModel_Contacts_DataProvider\":[\"ApplicationModel_Contacts\"],\"ApplicationModel_Contacts_Provider\":[\"ApplicationModel_Contacts\"],\"ApplicationModel_ConversationalAgent\":[\"ApplicationModel\"],\"ApplicationModel_Core\":[\"ApplicationModel\"],\"ApplicationModel_DataTransfer\":[\"ApplicationModel\"],\"ApplicationModel_DataTransfer_DragDrop\":[\"ApplicationModel_DataTransfer\"],\"ApplicationModel_DataTransfer_DragDrop_Core\":[\"ApplicationModel_DataTransfer_DragDrop\"],\"ApplicationModel_DataTransfer_ShareTarget\":[\"ApplicationModel_DataTransfer\"],\"ApplicationModel_Email\":[\"ApplicationModel\"],\"ApplicationModel_Email_DataProvider\":[\"ApplicationModel_Email\"],\"ApplicationModel_ExtendedExecution\":[\"ApplicationModel\"],\"ApplicationModel_ExtendedExecution_Foreground\":[\"ApplicationModel_ExtendedExecution\"],\"ApplicationModel_Holographic\":[\"ApplicationModel\"],\"ApplicationModel_LockScreen\":[\"ApplicationModel\"],\"ApplicationModel_PackageExtensions\":[\"ApplicationModel\"],\"ApplicationModel_Payments\":[\"ApplicationModel\"],\"ApplicationModel_Payments_Provider\":[\"ApplicationModel_Payments\"],\"ApplicationModel_Preview\":[\"ApplicationModel\"],\"ApplicationModel_Preview_Holographic\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Preview_InkWorkspace\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Preview_Notes\":[\"ApplicationModel_Preview\"],\"ApplicationModel_Resources\":[\"ApplicationModel\"],\"ApplicationModel_Resources_Core\":[\"ApplicationModel_Resources\"],\"ApplicationModel_Resources_Management\":[\"ApplicationModel_Resources\"],\"ApplicationModel_Search\":[\"ApplicationModel\"],\"ApplicationModel_Search_Core\":[\"ApplicationModel_Search\"],\"ApplicationModel_UserActivities\":[\"ApplicationModel\"],\"ApplicationModel_UserActivities_Core\":[\"ApplicationModel_UserActivities\"],\"ApplicationModel_UserDataAccounts\":[\"ApplicationModel\"],\"ApplicationModel_UserDataAccounts_Provider\":[\"ApplicationModel_UserDataAccounts\"],\"ApplicationModel_UserDataAccounts_SystemAccess\":[\"ApplicationModel_UserDataAccounts\"],\"ApplicationModel_UserDataTasks\":[\"ApplicationModel\"],\"ApplicationModel_UserDataTasks_DataProvider\":[\"ApplicationModel_UserDataTasks\"],\"ApplicationModel_VoiceCommands\":[\"ApplicationModel\"],\"ApplicationModel_Wallet\":[\"ApplicationModel\"],\"ApplicationModel_Wallet_System\":[\"ApplicationModel_Wallet\"],\"Data\":[\"Foundation\"],\"Data_Html\":[\"Data\"],\"Data_Json\":[\"Data\"],\"Data_Pdf\":[\"Data\"],\"Data_Text\":[\"Data\"],\"Data_Xml\":[\"Data\"],\"Data_Xml_Dom\":[\"Data_Xml\"],\"Data_Xml_Xsl\":[\"Data_Xml\"],\"Devices\":[\"Foundation\"],\"Devices_Adc\":[\"Devices\"],\"Devices_Adc_Provider\":[\"Devices_Adc\"],\"Devices_Background\":[\"Devices\"],\"Devices_Bluetooth\":[\"Devices\"],\"Devices_Bluetooth_Advertisement\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_Background\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_GenericAttributeProfile\":[\"Devices_Bluetooth\"],\"Devices_Bluetooth_Rfcomm\":[\"Devices_Bluetooth\"],\"Devices_Custom\":[\"Devices\"],\"Devices_Display\":[\"Devices\"],\"Devices_Display_Core\":[\"Devices_Display\"],\"Devices_Enumeration\":[\"Devices\"],\"Devices_Enumeration_Pnp\":[\"Devices_Enumeration\"],\"Devices_Geolocation\":[\"Devices\"],\"Devices_Geolocation_Geofencing\":[\"Devices_Geolocation\"],\"Devices_Geolocation_Provider\":[\"Devices_Geolocation\"],\"Devices_Gpio\":[\"Devices\"],\"Devices_Gpio_Provider\":[\"Devices_Gpio\"],\"Devices_Haptics\":[\"Devices\"],\"Devices_HumanInterfaceDevice\":[\"Devices\"],\"Devices_I2c\":[\"Devices\"],\"Devices_I2c_Provider\":[\"Devices_I2c\"],\"Devices_Input\":[\"Devices\"],\"Devices_Input_Preview\":[\"Devices_Input\"],\"Devices_Lights\":[\"Devices\"],\"Devices_Lights_Effects\":[\"Devices_Lights\"],\"Devices_Midi\":[\"Devices\"],\"Devices_PointOfService\":[\"Devices\"],\"Devices_PointOfService_Provider\":[\"Devices_PointOfService\"],\"Devices_Portable\":[\"Devices\"],\"Devices_Power\":[\"Devices\"],\"Devices_Printers\":[\"Devices\"],\"Devices_Printers_Extensions\":[\"Devices_Printers\"],\"Devices_Pwm\":[\"Devices\"],\"Devices_Pwm_Provider\":[\"Devices_Pwm\"],\"Devices_Radios\":[\"Devices\"],\"Devices_Scanners\":[\"Devices\"],\"Devices_Sensors\":[\"Devices\"],\"Devices_Sensors_Custom\":[\"Devices_Sensors\"],\"Devices_SerialCommunication\":[\"Devices\"],\"Devices_SmartCards\":[\"Devices\"],\"Devices_Sms\":[\"Devices\"],\"Devices_Spi\":[\"Devices\"],\"Devices_Spi_Provider\":[\"Devices_Spi\"],\"Devices_Usb\":[\"Devices\"],\"Devices_WiFi\":[\"Devices\"],\"Devices_WiFiDirect\":[\"Devices\"],\"Devices_WiFiDirect_Services\":[\"Devices_WiFiDirect\"],\"Foundation\":[],\"Foundation_Collections\":[\"Foundation\"],\"Foundation_Diagnostics\":[\"Foundation\"],\"Foundation_Metadata\":[\"Foundation\"],\"Foundation_Numerics\":[\"Foundation\"],\"Gaming\":[\"Foundation\"],\"Gaming_Input\":[\"Gaming\"],\"Gaming_Input_Custom\":[\"Gaming_Input\"],\"Gaming_Input_ForceFeedback\":[\"Gaming_Input\"],\"Gaming_Input_Preview\":[\"Gaming_Input\"],\"Gaming_Preview\":[\"Gaming\"],\"Gaming_Preview_GamesEnumeration\":[\"Gaming_Preview\"],\"Gaming_UI\":[\"Gaming\"],\"Gaming_XboxLive\":[\"Gaming\"],\"Gaming_XboxLive_Storage\":[\"Gaming_XboxLive\"],\"Globalization\":[\"Foundation\"],\"Globalization_Collation\":[\"Globalization\"],\"Globalization_DateTimeFormatting\":[\"Globalization\"],\"Globalization_Fonts\":[\"Globalization\"],\"Globalization_NumberFormatting\":[\"Globalization\"],\"Globalization_PhoneNumberFormatting\":[\"Globalization\"],\"Graphics\":[\"Foundation\"],\"Graphics_Capture\":[\"Graphics\"],\"Graphics_DirectX\":[\"Graphics\"],\"Graphics_DirectX_Direct3D11\":[\"Graphics_DirectX\"],\"Graphics_Display\":[\"Graphics\"],\"Graphics_Display_Core\":[\"Graphics_Display\"],\"Graphics_Effects\":[\"Graphics\"],\"Graphics_Holographic\":[\"Graphics\"],\"Graphics_Imaging\":[\"Graphics\"],\"Graphics_Printing\":[\"Graphics\"],\"Graphics_Printing3D\":[\"Graphics\"],\"Graphics_Printing_OptionDetails\":[\"Graphics_Printing\"],\"Graphics_Printing_PrintSupport\":[\"Graphics_Printing\"],\"Graphics_Printing_PrintTicket\":[\"Graphics_Printing\"],\"Graphics_Printing_ProtectedPrint\":[\"Graphics_Printing\"],\"Graphics_Printing_Workflow\":[\"Graphics_Printing\"],\"Management\":[\"Foundation\"],\"Management_Core\":[\"Management\"],\"Management_Deployment\":[\"Management\"],\"Management_Deployment_Preview\":[\"Management_Deployment\"],\"Management_Policies\":[\"Management\"],\"Management_Setup\":[\"Management\"],\"Management_Update\":[\"Management\"],\"Management_Workplace\":[\"Management\"],\"Media\":[\"Foundation\"],\"Media_AppBroadcasting\":[\"Media\"],\"Media_AppRecording\":[\"Media\"],\"Media_Audio\":[\"Media\"],\"Media_Capture\":[\"Media\"],\"Media_Capture_Core\":[\"Media_Capture\"],\"Media_Capture_Frames\":[\"Media_Capture\"],\"Media_Casting\":[\"Media\"],\"Media_ClosedCaptioning\":[\"Media\"],\"Media_ContentRestrictions\":[\"Media\"],\"Media_Control\":[\"Media\"],\"Media_Core\":[\"Media\"],\"Media_Core_Preview\":[\"Media_Core\"],\"Media_Devices\":[\"Media\"],\"Media_Devices_Core\":[\"Media_Devices\"],\"Media_DialProtocol\":[\"Media\"],\"Media_Editing\":[\"Media\"],\"Media_Effects\":[\"Media\"],\"Media_FaceAnalysis\":[\"Media\"],\"Media_Import\":[\"Media\"],\"Media_MediaProperties\":[\"Media\"],\"Media_Miracast\":[\"Media\"],\"Media_Ocr\":[\"Media\"],\"Media_PlayTo\":[\"Media\"],\"Media_Playback\":[\"Media\"],\"Media_Playlists\":[\"Media\"],\"Media_Protection\":[\"Media\"],\"Media_Protection_PlayReady\":[\"Media_Protection\"],\"Media_Render\":[\"Media\"],\"Media_SpeechRecognition\":[\"Media\"],\"Media_SpeechSynthesis\":[\"Media\"],\"Media_Streaming\":[\"Media\"],\"Media_Streaming_Adaptive\":[\"Media_Streaming\"],\"Media_Transcoding\":[\"Media\"],\"Networking\":[\"Foundation\"],\"Networking_BackgroundTransfer\":[\"Networking\"],\"Networking_Connectivity\":[\"Networking\"],\"Networking_NetworkOperators\":[\"Networking\"],\"Networking_Proximity\":[\"Networking\"],\"Networking_PushNotifications\":[\"Networking\"],\"Networking_ServiceDiscovery\":[\"Networking\"],\"Networking_ServiceDiscovery_Dnssd\":[\"Networking_ServiceDiscovery\"],\"Networking_Sockets\":[\"Networking\"],\"Networking_Vpn\":[\"Networking\"],\"Networking_XboxLive\":[\"Networking\"],\"Perception\":[\"Foundation\"],\"Perception_Automation\":[\"Perception\"],\"Perception_Automation_Core\":[\"Perception_Automation\"],\"Perception_People\":[\"Perception\"],\"Perception_Spatial\":[\"Perception\"],\"Perception_Spatial_Preview\":[\"Perception_Spatial\"],\"Perception_Spatial_Surfaces\":[\"Perception_Spatial\"],\"Security\":[\"Foundation\"],\"Security_Authentication\":[\"Security\"],\"Security_Authentication_Identity\":[\"Security_Authentication\"],\"Security_Authentication_Identity_Core\":[\"Security_Authentication_Identity\"],\"Security_Authentication_OnlineId\":[\"Security_Authentication\"],\"Security_Authentication_Web\":[\"Security_Authentication\"],\"Security_Authentication_Web_Core\":[\"Security_Authentication_Web\"],\"Security_Authentication_Web_Provider\":[\"Security_Authentication_Web\"],\"Security_Authorization\":[\"Security\"],\"Security_Authorization_AppCapabilityAccess\":[\"Security_Authorization\"],\"Security_Credentials\":[\"Security\"],\"Security_Credentials_UI\":[\"Security_Credentials\"],\"Security_Cryptography\":[\"Security\"],\"Security_Cryptography_Certificates\":[\"Security_Cryptography\"],\"Security_Cryptography_Core\":[\"Security_Cryptography\"],\"Security_Cryptography_DataProtection\":[\"Security_Cryptography\"],\"Security_DataProtection\":[\"Security\"],\"Security_EnterpriseData\":[\"Security\"],\"Security_ExchangeActiveSyncProvisioning\":[\"Security\"],\"Security_Isolation\":[\"Security\"],\"Services\":[\"Foundation\"],\"Services_Maps\":[\"Services\"],\"Services_Maps_Guidance\":[\"Services_Maps\"],\"Services_Maps_LocalSearch\":[\"Services_Maps\"],\"Services_Maps_OfflineMaps\":[\"Services_Maps\"],\"Services_Store\":[\"Services\"],\"Services_TargetedContent\":[\"Services\"],\"Storage\":[\"Foundation\"],\"Storage_AccessCache\":[\"Storage\"],\"Storage_BulkAccess\":[\"Storage\"],\"Storage_Compression\":[\"Storage\"],\"Storage_FileProperties\":[\"Storage\"],\"Storage_Pickers\":[\"Storage\"],\"Storage_Pickers_Provider\":[\"Storage_Pickers\"],\"Storage_Provider\":[\"Storage\"],\"Storage_Search\":[\"Storage\"],\"Storage_Streams\":[\"Storage\"],\"System\":[\"Foundation\"],\"System_Diagnostics\":[\"System\"],\"System_Diagnostics_DevicePortal\":[\"System_Diagnostics\"],\"System_Diagnostics_Telemetry\":[\"System_Diagnostics\"],\"System_Diagnostics_TraceReporting\":[\"System_Diagnostics\"],\"System_Display\":[\"System\"],\"System_Implementation\":[\"System\"],\"System_Implementation_FileExplorer\":[\"System_Implementation\"],\"System_Inventory\":[\"System\"],\"System_Power\":[\"System\"],\"System_Profile\":[\"System\"],\"System_Profile_SystemManufacturers\":[\"System_Profile\"],\"System_RemoteDesktop\":[\"System\"],\"System_RemoteDesktop_Input\":[\"System_RemoteDesktop\"],\"System_RemoteDesktop_Provider\":[\"System_RemoteDesktop\"],\"System_RemoteSystems\":[\"System\"],\"System_Threading\":[\"System\"],\"System_Threading_Core\":[\"System_Threading\"],\"System_Update\":[\"System\"],\"System_UserProfile\":[\"System\"],\"UI\":[\"Foundation\"],\"UI_Accessibility\":[\"UI\"],\"UI_ApplicationSettings\":[\"UI\"],\"UI_Composition\":[\"UI\"],\"UI_Composition_Core\":[\"UI_Composition\"],\"UI_Composition_Desktop\":[\"UI_Composition\"],\"UI_Composition_Diagnostics\":[\"UI_Composition\"],\"UI_Composition_Effects\":[\"UI_Composition\"],\"UI_Composition_Interactions\":[\"UI_Composition\"],\"UI_Composition_Scenes\":[\"UI_Composition\"],\"UI_Core\":[\"UI\"],\"UI_Core_AnimationMetrics\":[\"UI_Core\"],\"UI_Core_Preview\":[\"UI_Core\"],\"UI_Input\":[\"UI\"],\"UI_Input_Core\":[\"UI_Input\"],\"UI_Input_Inking\":[\"UI_Input\"],\"UI_Input_Inking_Analysis\":[\"UI_Input_Inking\"],\"UI_Input_Inking_Core\":[\"UI_Input_Inking\"],\"UI_Input_Inking_Preview\":[\"UI_Input_Inking\"],\"UI_Input_Preview\":[\"UI_Input\"],\"UI_Input_Preview_Injection\":[\"UI_Input_Preview\"],\"UI_Input_Preview_Text\":[\"UI_Input_Preview\"],\"UI_Input_Spatial\":[\"UI_Input\"],\"UI_Notifications\":[\"UI\"],\"UI_Notifications_Management\":[\"UI_Notifications\"],\"UI_Notifications_Preview\":[\"UI_Notifications\"],\"UI_Popups\":[\"UI\"],\"UI_Shell\":[\"UI\"],\"UI_StartScreen\":[\"UI\"],\"UI_Text\":[\"UI\"],\"UI_Text_Core\":[\"UI_Text\"],\"UI_UIAutomation\":[\"UI\"],\"UI_UIAutomation_Core\":[\"UI_UIAutomation\"],\"UI_ViewManagement\":[\"UI\"],\"UI_ViewManagement_Core\":[\"UI_ViewManagement\"],\"UI_WebUI\":[\"UI\"],\"UI_WindowManagement\":[\"UI\"],\"UI_WindowManagement_Preview\":[\"UI_WindowManagement\"],\"Wdk\":[\"Win32_Foundation\"],\"Wdk_Devices\":[\"Wdk\"],\"Wdk_Devices_Bluetooth\":[\"Wdk_Devices\"],\"Wdk_Devices_HumanInterfaceDevice\":[\"Wdk_Devices\"],\"Wdk_Foundation\":[\"Wdk\"],\"Wdk_Graphics\":[\"Wdk\"],\"Wdk_Graphics_Direct3D\":[\"Wdk_Graphics\"],\"Wdk_NetworkManagement\":[\"Wdk\"],\"Wdk_NetworkManagement_Ndis\":[\"Wdk_NetworkManagement\"],\"Wdk_NetworkManagement_WindowsFilteringPlatform\":[\"Wdk_NetworkManagement\"],\"Wdk_Storage\":[\"Wdk\"],\"Wdk_Storage_FileSystem\":[\"Wdk_Storage\"],\"Wdk_Storage_FileSystem_Minifilters\":[\"Wdk_Storage_FileSystem\"],\"Wdk_System\":[\"Wdk\"],\"Wdk_System_IO\":[\"Wdk_System\"],\"Wdk_System_Memory\":[\"Wdk_System\"],\"Wdk_System_OfflineRegistry\":[\"Wdk_System\"],\"Wdk_System_Registry\":[\"Wdk_System\"],\"Wdk_System_SystemInformation\":[\"Wdk_System\"],\"Wdk_System_SystemServices\":[\"Wdk_System\"],\"Wdk_System_Threading\":[\"Wdk_System\"],\"Web\":[\"Foundation\"],\"Web_AtomPub\":[\"Web\"],\"Web_Http\":[\"Web\"],\"Web_Http_Diagnostics\":[\"Web_Http\"],\"Web_Http_Filters\":[\"Web_Http\"],\"Web_Http_Headers\":[\"Web_Http\"],\"Web_Syndication\":[\"Web\"],\"Web_UI\":[\"Web\"],\"Web_UI_Interop\":[\"Web_UI\"],\"Win32\":[\"Win32_Foundation\"],\"Win32_AI\":[\"Win32\"],\"Win32_AI_MachineLearning\":[\"Win32_AI\"],\"Win32_AI_MachineLearning_DirectML\":[\"Win32_AI_MachineLearning\"],\"Win32_AI_MachineLearning_WinML\":[\"Win32_AI_MachineLearning\"],\"Win32_Data\":[\"Win32\"],\"Win32_Data_HtmlHelp\":[\"Win32_Data\"],\"Win32_Data_RightsManagement\":[\"Win32_Data\"],\"Win32_Data_Xml\":[\"Win32_Data\"],\"Win32_Data_Xml_MsXml\":[\"Win32_Data_Xml\"],\"Win32_Data_Xml_XmlLite\":[\"Win32_Data_Xml\"],\"Win32_Devices\":[\"Win32\"],\"Win32_Devices_AllJoyn\":[\"Win32_Devices\"],\"Win32_Devices_Beep\":[\"Win32_Devices\"],\"Win32_Devices_BiometricFramework\":[\"Win32_Devices\"],\"Win32_Devices_Bluetooth\":[\"Win32_Devices\"],\"Win32_Devices_Cdrom\":[\"Win32_Devices\"],\"Win32_Devices_Communication\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAccess\":[\"Win32_Devices\"],\"Win32_Devices_DeviceAndDriverInstallation\":[\"Win32_Devices\"],\"Win32_Devices_DeviceQuery\":[\"Win32_Devices\"],\"Win32_Devices_Display\":[\"Win32_Devices\"],\"Win32_Devices_Dvd\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration\":[\"Win32_Devices\"],\"Win32_Devices_Enumeration_Pnp\":[\"Win32_Devices_Enumeration\"],\"Win32_Devices_Fax\":[\"Win32_Devices\"],\"Win32_Devices_FunctionDiscovery\":[\"Win32_Devices\"],\"Win32_Devices_Geolocation\":[\"Win32_Devices\"],\"Win32_Devices_HumanInterfaceDevice\":[\"Win32_Devices\"],\"Win32_Devices_ImageAcquisition\":[\"Win32_Devices\"],\"Win32_Devices_Nfc\":[\"Win32_Devices\"],\"Win32_Devices_Nfp\":[\"Win32_Devices\"],\"Win32_Devices_PortableDevices\":[\"Win32_Devices\"],\"Win32_Devices_Properties\":[\"Win32_Devices\"],\"Win32_Devices_Pwm\":[\"Win32_Devices\"],\"Win32_Devices_Sensors\":[\"Win32_Devices\"],\"Win32_Devices_SerialCommunication\":[\"Win32_Devices\"],\"Win32_Devices_Tapi\":[\"Win32_Devices\"],\"Win32_Devices_Usb\":[\"Win32_Devices\"],\"Win32_Devices_WebServicesOnDevices\":[\"Win32_Devices\"],\"Win32_Foundation\":[\"Win32\"],\"Win32_Gaming\":[\"Win32\"],\"Win32_Globalization\":[\"Win32\"],\"Win32_Graphics\":[\"Win32\"],\"Win32_Graphics_CompositionSwapchain\":[\"Win32_Graphics\"],\"Win32_Graphics_DXCore\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct2D\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct2D_Common\":[\"Win32_Graphics_Direct2D\"],\"Win32_Graphics_Direct3D\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D10\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D11\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D11on12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D9\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D9on12\":[\"Win32_Graphics\"],\"Win32_Graphics_Direct3D_Dxc\":[\"Win32_Graphics_Direct3D\"],\"Win32_Graphics_Direct3D_Fxc\":[\"Win32_Graphics_Direct3D\"],\"Win32_Graphics_DirectComposition\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectDraw\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectManipulation\":[\"Win32_Graphics\"],\"Win32_Graphics_DirectWrite\":[\"Win32_Graphics\"],\"Win32_Graphics_Dwm\":[\"Win32_Graphics\"],\"Win32_Graphics_Dxgi\":[\"Win32_Graphics\"],\"Win32_Graphics_Dxgi_Common\":[\"Win32_Graphics_Dxgi\"],\"Win32_Graphics_Gdi\":[\"Win32_Graphics\"],\"Win32_Graphics_GdiPlus\":[\"Win32_Graphics\"],\"Win32_Graphics_Hlsl\":[\"Win32_Graphics\"],\"Win32_Graphics_Imaging\":[\"Win32_Graphics\"],\"Win32_Graphics_Imaging_D2D\":[\"Win32_Graphics_Imaging\"],\"Win32_Graphics_OpenGL\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing\":[\"Win32_Graphics\"],\"Win32_Graphics_Printing_PrintTicket\":[\"Win32_Graphics_Printing\"],\"Win32_Management\":[\"Win32\"],\"Win32_Management_MobileDeviceManagementRegistration\":[\"Win32_Management\"],\"Win32_Media\":[\"Win32\"],\"Win32_Media_Audio\":[\"Win32_Media\"],\"Win32_Media_Audio_Apo\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectMusic\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_DirectSound\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_Endpoints\":[\"Win32_Media_Audio\"],\"Win32_Media_Audio_XAudio2\":[\"Win32_Media_Audio\"],\"Win32_Media_DeviceManager\":[\"Win32_Media\"],\"Win32_Media_DirectShow\":[\"Win32_Media\"],\"Win32_Media_DirectShow_Tv\":[\"Win32_Media_DirectShow\"],\"Win32_Media_DirectShow_Xml\":[\"Win32_Media_DirectShow\"],\"Win32_Media_DxMediaObjects\":[\"Win32_Media\"],\"Win32_Media_KernelStreaming\":[\"Win32_Media\"],\"Win32_Media_LibrarySharingServices\":[\"Win32_Media\"],\"Win32_Media_MediaFoundation\":[\"Win32_Media\"],\"Win32_Media_MediaPlayer\":[\"Win32_Media\"],\"Win32_Media_Multimedia\":[\"Win32_Media\"],\"Win32_Media_PictureAcquisition\":[\"Win32_Media\"],\"Win32_Media_Speech\":[\"Win32_Media\"],\"Win32_Media_Streaming\":[\"Win32_Media\"],\"Win32_Media_WindowsMediaFormat\":[\"Win32_Media\"],\"Win32_NetworkManagement\":[\"Win32\"],\"Win32_NetworkManagement_Dhcp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Dns\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_InternetConnectionWizard\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_IpHelper\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_MobileBroadband\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Multicast\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Ndis\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetBios\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetManagement\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetShell\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkDiagnosticsFramework\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_NetworkPolicyServer\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_P2P\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_QoS\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Rras\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_Snmp\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WNet\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WebDav\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WiFi\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectNow\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsConnectionManager\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFilteringPlatform\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsFirewall\":[\"Win32_NetworkManagement\"],\"Win32_NetworkManagement_WindowsNetworkVirtualization\":[\"Win32_NetworkManagement\"],\"Win32_Networking\":[\"Win32\"],\"Win32_Networking_ActiveDirectory\":[\"Win32_Networking\"],\"Win32_Networking_BackgroundIntelligentTransferService\":[\"Win32_Networking\"],\"Win32_Networking_Clustering\":[\"Win32_Networking\"],\"Win32_Networking_HttpServer\":[\"Win32_Networking\"],\"Win32_Networking_Ldap\":[\"Win32_Networking\"],\"Win32_Networking_NetworkListManager\":[\"Win32_Networking\"],\"Win32_Networking_RemoteDifferentialCompression\":[\"Win32_Networking\"],\"Win32_Networking_WebSocket\":[\"Win32_Networking\"],\"Win32_Networking_WinHttp\":[\"Win32_Networking\"],\"Win32_Networking_WinInet\":[\"Win32_Networking\"],\"Win32_Networking_WinSock\":[\"Win32_Networking\"],\"Win32_Networking_WindowsWebServices\":[\"Win32_Networking\"],\"Win32_Security\":[\"Win32\"],\"Win32_Security_AppLocker\":[\"Win32_Security\"],\"Win32_Security_Authentication\":[\"Win32_Security\"],\"Win32_Security_Authentication_Identity\":[\"Win32_Security_Authentication\"],\"Win32_Security_Authentication_Identity_Provider\":[\"Win32_Security_Authentication_Identity\"],\"Win32_Security_Authorization\":[\"Win32_Security\"],\"Win32_Security_Authorization_UI\":[\"Win32_Security_Authorization\"],\"Win32_Security_ConfigurationSnapin\":[\"Win32_Security\"],\"Win32_Security_Credentials\":[\"Win32_Security\"],\"Win32_Security_Cryptography\":[\"Win32_Security\"],\"Win32_Security_Cryptography_Catalog\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Certificates\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_Sip\":[\"Win32_Security_Cryptography\"],\"Win32_Security_Cryptography_UI\":[\"Win32_Security_Cryptography\"],\"Win32_Security_DiagnosticDataQuery\":[\"Win32_Security\"],\"Win32_Security_DirectoryServices\":[\"Win32_Security\"],\"Win32_Security_EnterpriseData\":[\"Win32_Security\"],\"Win32_Security_ExtensibleAuthenticationProtocol\":[\"Win32_Security\"],\"Win32_Security_Isolation\":[\"Win32_Security\"],\"Win32_Security_LicenseProtection\":[\"Win32_Security\"],\"Win32_Security_NetworkAccessProtection\":[\"Win32_Security\"],\"Win32_Security_Tpm\":[\"Win32_Security\"],\"Win32_Security_WinTrust\":[\"Win32_Security\"],\"Win32_Security_WinWlx\":[\"Win32_Security\"],\"Win32_Storage\":[\"Win32\"],\"Win32_Storage_Cabinets\":[\"Win32_Storage\"],\"Win32_Storage_CloudFilters\":[\"Win32_Storage\"],\"Win32_Storage_Compression\":[\"Win32_Storage\"],\"Win32_Storage_DataDeduplication\":[\"Win32_Storage\"],\"Win32_Storage_DistributedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_EnhancedStorage\":[\"Win32_Storage\"],\"Win32_Storage_FileHistory\":[\"Win32_Storage\"],\"Win32_Storage_FileServerResourceManager\":[\"Win32_Storage\"],\"Win32_Storage_FileSystem\":[\"Win32_Storage\"],\"Win32_Storage_Imapi\":[\"Win32_Storage\"],\"Win32_Storage_IndexServer\":[\"Win32_Storage\"],\"Win32_Storage_InstallableFileSystems\":[\"Win32_Storage\"],\"Win32_Storage_IscsiDisc\":[\"Win32_Storage\"],\"Win32_Storage_Jet\":[\"Win32_Storage\"],\"Win32_Storage_Nvme\":[\"Win32_Storage\"],\"Win32_Storage_OfflineFiles\":[\"Win32_Storage\"],\"Win32_Storage_OperationRecorder\":[\"Win32_Storage\"],\"Win32_Storage_Packaging\":[\"Win32_Storage\"],\"Win32_Storage_Packaging_Appx\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_Packaging_Opc\":[\"Win32_Storage_Packaging\"],\"Win32_Storage_ProjectedFileSystem\":[\"Win32_Storage\"],\"Win32_Storage_StructuredStorage\":[\"Win32_Storage\"],\"Win32_Storage_Vhd\":[\"Win32_Storage\"],\"Win32_Storage_VirtualDiskService\":[\"Win32_Storage\"],\"Win32_Storage_Vss\":[\"Win32_Storage\"],\"Win32_Storage_Xps\":[\"Win32_Storage\"],\"Win32_Storage_Xps_Printing\":[\"Win32_Storage_Xps\"],\"Win32_System\":[\"Win32\"],\"Win32_System_AddressBook\":[\"Win32_System\"],\"Win32_System_Antimalware\":[\"Win32_System\"],\"Win32_System_ApplicationInstallationAndServicing\":[\"Win32_System\"],\"Win32_System_ApplicationVerifier\":[\"Win32_System\"],\"Win32_System_AssessmentTool\":[\"Win32_System\"],\"Win32_System_ClrHosting\":[\"Win32_System\"],\"Win32_System_Com\":[\"Win32_System\"],\"Win32_System_Com_CallObj\":[\"Win32_System_Com\"],\"Win32_System_Com_ChannelCredentials\":[\"Win32_System_Com\"],\"Win32_System_Com_Events\":[\"Win32_System_Com\"],\"Win32_System_Com_Marshal\":[\"Win32_System_Com\"],\"Win32_System_Com_StructuredStorage\":[\"Win32_System_Com\"],\"Win32_System_Com_UI\":[\"Win32_System_Com\"],\"Win32_System_Com_Urlmon\":[\"Win32_System_Com\"],\"Win32_System_ComponentServices\":[\"Win32_System\"],\"Win32_System_Console\":[\"Win32_System\"],\"Win32_System_Contacts\":[\"Win32_System\"],\"Win32_System_CorrelationVector\":[\"Win32_System\"],\"Win32_System_DataExchange\":[\"Win32_System\"],\"Win32_System_DeploymentServices\":[\"Win32_System\"],\"Win32_System_DesktopSharing\":[\"Win32_System\"],\"Win32_System_DeveloperLicensing\":[\"Win32_System\"],\"Win32_System_Diagnostics\":[\"Win32_System\"],\"Win32_System_Diagnostics_Ceip\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ClrProfiling\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_Debug_ActiveScript\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Debug_Extensions\":[\"Win32_System_Diagnostics_Debug\"],\"Win32_System_Diagnostics_Etw\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ProcessSnapshotting\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_ToolHelp\":[\"Win32_System_Diagnostics\"],\"Win32_System_Diagnostics_TraceLogging\":[\"Win32_System_Diagnostics\"],\"Win32_System_DistributedTransactionCoordinator\":[\"Win32_System\"],\"Win32_System_Environment\":[\"Win32_System\"],\"Win32_System_ErrorReporting\":[\"Win32_System\"],\"Win32_System_EventCollector\":[\"Win32_System\"],\"Win32_System_EventLog\":[\"Win32_System\"],\"Win32_System_EventNotificationService\":[\"Win32_System\"],\"Win32_System_GroupPolicy\":[\"Win32_System\"],\"Win32_System_HostCompute\":[\"Win32_System\"],\"Win32_System_HostComputeNetwork\":[\"Win32_System\"],\"Win32_System_HostComputeSystem\":[\"Win32_System\"],\"Win32_System_Hypervisor\":[\"Win32_System\"],\"Win32_System_IO\":[\"Win32_System\"],\"Win32_System_Iis\":[\"Win32_System\"],\"Win32_System_Ioctl\":[\"Win32_System\"],\"Win32_System_JobObjects\":[\"Win32_System\"],\"Win32_System_Js\":[\"Win32_System\"],\"Win32_System_Kernel\":[\"Win32_System\"],\"Win32_System_LibraryLoader\":[\"Win32_System\"],\"Win32_System_Mailslots\":[\"Win32_System\"],\"Win32_System_Mapi\":[\"Win32_System\"],\"Win32_System_Memory\":[\"Win32_System\"],\"Win32_System_Memory_NonVolatile\":[\"Win32_System_Memory\"],\"Win32_System_MessageQueuing\":[\"Win32_System\"],\"Win32_System_MixedReality\":[\"Win32_System\"],\"Win32_System_Mmc\":[\"Win32_System\"],\"Win32_System_Ole\":[\"Win32_System\"],\"Win32_System_ParentalControls\":[\"Win32_System\"],\"Win32_System_PasswordManagement\":[\"Win32_System\"],\"Win32_System_Performance\":[\"Win32_System\"],\"Win32_System_Performance_HardwareCounterProfiling\":[\"Win32_System_Performance\"],\"Win32_System_Pipes\":[\"Win32_System\"],\"Win32_System_Power\":[\"Win32_System\"],\"Win32_System_ProcessStatus\":[\"Win32_System\"],\"Win32_System_RealTimeCommunications\":[\"Win32_System\"],\"Win32_System_Recovery\":[\"Win32_System\"],\"Win32_System_Registry\":[\"Win32_System\"],\"Win32_System_RemoteAssistance\":[\"Win32_System\"],\"Win32_System_RemoteDesktop\":[\"Win32_System\"],\"Win32_System_RemoteManagement\":[\"Win32_System\"],\"Win32_System_RestartManager\":[\"Win32_System\"],\"Win32_System_Restore\":[\"Win32_System\"],\"Win32_System_Rpc\":[\"Win32_System\"],\"Win32_System_Search\":[\"Win32_System\"],\"Win32_System_Search_Common\":[\"Win32_System_Search\"],\"Win32_System_SecurityCenter\":[\"Win32_System\"],\"Win32_System_ServerBackup\":[\"Win32_System\"],\"Win32_System_Services\":[\"Win32_System\"],\"Win32_System_SettingsManagementInfrastructure\":[\"Win32_System\"],\"Win32_System_SetupAndMigration\":[\"Win32_System\"],\"Win32_System_Shutdown\":[\"Win32_System\"],\"Win32_System_SideShow\":[\"Win32_System\"],\"Win32_System_StationsAndDesktops\":[\"Win32_System\"],\"Win32_System_SubsystemForLinux\":[\"Win32_System\"],\"Win32_System_SystemInformation\":[\"Win32_System\"],\"Win32_System_SystemServices\":[\"Win32_System\"],\"Win32_System_TaskScheduler\":[\"Win32_System\"],\"Win32_System_Threading\":[\"Win32_System\"],\"Win32_System_Time\":[\"Win32_System\"],\"Win32_System_TpmBaseServices\":[\"Win32_System\"],\"Win32_System_TransactionServer\":[\"Win32_System\"],\"Win32_System_UpdateAgent\":[\"Win32_System\"],\"Win32_System_UpdateAssessment\":[\"Win32_System\"],\"Win32_System_UserAccessLogging\":[\"Win32_System\"],\"Win32_System_Variant\":[\"Win32_System\"],\"Win32_System_VirtualDosMachines\":[\"Win32_System\"],\"Win32_System_WinRT\":[\"Win32_System\"],\"Win32_System_WinRT_AllJoyn\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Composition\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_CoreInputView\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Direct3D11\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Display\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Graphics\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Graphics_Capture\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Graphics_Direct2D\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Graphics_Imaging\":[\"Win32_System_WinRT_Graphics\"],\"Win32_System_WinRT_Holographic\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Isolation\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_ML\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Media\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Metadata\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Pdf\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Printing\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Shell\":[\"Win32_System_WinRT\"],\"Win32_System_WinRT_Storage\":[\"Win32_System_WinRT\"],\"Win32_System_WindowsProgramming\":[\"Win32_System\"],\"Win32_System_WindowsSync\":[\"Win32_System\"],\"Win32_System_Wmi\":[\"Win32_System\"],\"Win32_UI\":[\"Win32\"],\"Win32_UI_Accessibility\":[\"Win32_UI\"],\"Win32_UI_Animation\":[\"Win32_UI\"],\"Win32_UI_ColorSystem\":[\"Win32_UI\"],\"Win32_UI_Controls\":[\"Win32_UI\"],\"Win32_UI_Controls_Dialogs\":[\"Win32_UI_Controls\"],\"Win32_UI_Controls_RichEdit\":[\"Win32_UI_Controls\"],\"Win32_UI_HiDpi\":[\"Win32_UI\"],\"Win32_UI_Input\":[\"Win32_UI\"],\"Win32_UI_Input_Ime\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Ink\":[\"Win32_UI_Input\"],\"Win32_UI_Input_KeyboardAndMouse\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Pointer\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Radial\":[\"Win32_UI_Input\"],\"Win32_UI_Input_Touch\":[\"Win32_UI_Input\"],\"Win32_UI_Input_XboxController\":[\"Win32_UI_Input\"],\"Win32_UI_InteractionContext\":[\"Win32_UI\"],\"Win32_UI_LegacyWindowsEnvironmentFeatures\":[\"Win32_UI\"],\"Win32_UI_Magnification\":[\"Win32_UI\"],\"Win32_UI_Notifications\":[\"Win32_UI\"],\"Win32_UI_Ribbon\":[\"Win32_UI\"],\"Win32_UI_Shell\":[\"Win32_UI\"],\"Win32_UI_Shell_Common\":[\"Win32_UI_Shell\"],\"Win32_UI_Shell_PropertiesSystem\":[\"Win32_UI_Shell\"],\"Win32_UI_TabletPC\":[\"Win32_UI\"],\"Win32_UI_TextServices\":[\"Win32_UI\"],\"Win32_UI_WindowsAndMessaging\":[\"Win32_UI\"],\"Win32_UI_Wpf\":[\"Win32_UI\"],\"Win32_Web\":[\"Win32\"],\"Win32_Web_InternetExplorer\":[\"Win32_Web\"],\"default\":[\"std\"],\"docs\":[],\"std\":[\"windows-collections/std\",\"windows-core/std\",\"windows-future/std\",\"windows-numerics/std\"]}}", "windows_aarch64_gnullvm_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_aarch64_gnullvm_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_aarch64_gnullvm_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_aarch64_gnullvm_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_aarch64_gnullvm_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_aarch64_msvc_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_aarch64_msvc_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_aarch64_msvc_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_aarch64_msvc_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_aarch64_msvc_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_i686_gnu_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_i686_gnu_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_i686_gnu_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_i686_gnu_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_i686_gnu_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_i686_gnullvm_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_i686_gnullvm_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_i686_gnullvm_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_i686_msvc_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_i686_msvc_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_i686_msvc_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_i686_msvc_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_i686_msvc_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_gnu_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_gnu_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_gnu_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_x86_64_gnu_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_x86_64_gnu_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_gnullvm_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_gnullvm_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_gnullvm_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_x86_64_gnullvm_0.53.0": "{\"dependencies\":[],\"features\":{}}", + "windows_x86_64_gnullvm_0.53.1": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_msvc_0.42.2": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_msvc_0.48.5": "{\"dependencies\":[],\"features\":{}}", "windows_x86_64_msvc_0.52.6": "{\"dependencies\":[],\"features\":{}}", - "windows_x86_64_msvc_0.53.0": "{\"dependencies\":[],\"features\":{}}", - "winnow_0.7.13": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.3\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.3.2\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.86\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.14\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.0\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.5\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^1.1.0\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.0\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\"],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[]}}", + "windows_x86_64_msvc_0.53.1": "{\"dependencies\":[],\"features\":{}}", + "winnow_0.7.14": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.4\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.100\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.1\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2.1.1\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.3\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\"],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[]}}", "winreg_0.10.1": "{\"dependencies\":[{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"impl-default\",\"impl-debug\",\"minwindef\",\"minwinbase\",\"timezoneapi\",\"winerror\",\"winnt\",\"winreg\",\"handleapi\"],\"name\":\"winapi\",\"req\":\"^0.3.9\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[\"winapi/ktmw32\"]}}", "winreg_0.50.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Time\",\"Win32_System_Registry\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_Diagnostics_Debug\"],\"name\":\"windows-sys\",\"req\":\"^0.48.0\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[]}}", "winres_0.1.12": "{\"dependencies\":[{\"name\":\"toml\",\"req\":\"^0.5\"},{\"features\":[\"winnt\"],\"kind\":\"dev\",\"name\":\"winapi\",\"req\":\"^0.3\"}],\"features\":{}}", "winsafe_0.0.19": "{\"dependencies\":[],\"features\":{\"comctl\":[\"ole\"],\"dshow\":[\"oleaut\"],\"dwm\":[\"uxtheme\"],\"dxgi\":[\"ole\"],\"gdi\":[\"user\"],\"gui\":[\"comctl\",\"shell\",\"uxtheme\"],\"kernel\":[],\"mf\":[\"oleaut\"],\"ole\":[\"user\"],\"oleaut\":[\"ole\"],\"shell\":[\"oleaut\"],\"taskschd\":[\"oleaut\"],\"user\":[\"kernel\"],\"uxtheme\":[\"gdi\",\"ole\"],\"version\":[\"kernel\"]}}", "winsplit_0.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "wiremock_0.6.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2.10.0\"},{\"name\":\"assert-json-diff\",\"req\":\"^2.0.2\"},{\"features\":[\"attributes\",\"tokio1\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.13.2\"},{\"name\":\"base64\",\"req\":\"^0.22\"},{\"name\":\"deadpool\",\"req\":\"^0.12.2\"},{\"name\":\"futures\",\"req\":\"^0.3.31\"},{\"name\":\"http\",\"req\":\"^1.3\"},{\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"full\"],\"name\":\"hyper\",\"req\":\"^1.7\"},{\"features\":[\"tokio\",\"server\",\"http1\",\"http2\"],\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"regex\",\"req\":\"^1\"},{\"features\":[\"json\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12.23\"},{\"name\":\"serde\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"name\":\"serde_json\",\"req\":\"^1\"},{\"features\":[\"rt\",\"macros\",\"net\"],\"name\":\"tokio\",\"req\":\"^1.47.1\"},{\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.47.1\"},{\"name\":\"url\",\"req\":\"^2.5\"}],\"features\":{}}", - "wit-bindgen-rt_0.39.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.3.3\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.19.0\"}],\"features\":{\"async\":[\"dep:futures\",\"dep:once_cell\"]}}", - "wl-clipboard-rs_0.9.2": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.170\"},{\"name\":\"log\",\"req\":\"^0.4.26\"},{\"features\":[\"io_safety\"],\"name\":\"os_pipe\",\"req\":\"^1.2.1\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.5.1\"},{\"features\":[\"fs\",\"event\"],\"name\":\"rustix\",\"req\":\"^0.38.44\"},{\"name\":\"tempfile\",\"req\":\"^3.17.1\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"name\":\"tree_magic_mini\",\"req\":\"^3.1.6\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.8\"},{\"name\":\"wayland-client\",\"req\":\"^0.31.8\"},{\"features\":[\"client\",\"staging\"],\"name\":\"wayland-protocols\",\"req\":\"^0.32.6\"},{\"features\":[\"server\",\"staging\"],\"kind\":\"dev\",\"name\":\"wayland-protocols\",\"req\":\"^0.32.6\"},{\"features\":[\"client\"],\"name\":\"wayland-protocols-wlr\",\"req\":\"^0.3.6\"},{\"features\":[\"server\"],\"kind\":\"dev\",\"name\":\"wayland-protocols-wlr\",\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"wayland-server\",\"req\":\"^0.31.7\"}],\"features\":{\"dlopen\":[\"native_lib\",\"wayland-backend/dlopen\",\"wayland-backend/dlopen\"],\"native_lib\":[\"wayland-backend/client_system\",\"wayland-backend/server_system\"]}}", + "wit-bindgen_0.51.0": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0\"},{\"name\":\"bitflags\",\"optional\":true,\"req\":\"^2.3.3\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3.30\"},{\"name\":\"wit-bindgen-rust-macro\",\"optional\":true,\"req\":\"^0.51.0\"}],\"features\":{\"async\":[\"std\",\"wit-bindgen-rust-macro?/async\"],\"async-spawn\":[\"async\",\"dep:futures\"],\"bitflags\":[\"dep:bitflags\"],\"default\":[\"macros\",\"realloc\",\"async\",\"std\",\"bitflags\"],\"inter-task-wakeup\":[\"async\"],\"macros\":[\"dep:wit-bindgen-rust-macro\"],\"realloc\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\"],\"std\":[]}}", + "wl-clipboard-rs_0.9.3": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.168\"},{\"name\":\"log\",\"req\":\"^0.4.11\"},{\"features\":[\"io_safety\"],\"name\":\"os_pipe\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.7\"},{\"features\":[\"fs\",\"event\"],\"name\":\"rustix\",\"req\":\"^1.0.2\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"name\":\"tree_magic_mini\",\"req\":\"^3\"},{\"name\":\"wayland-backend\",\"req\":\"^0.3.11\"},{\"name\":\"wayland-client\",\"req\":\"^0.31.11\"},{\"features\":[\"client\",\"staging\"],\"name\":\"wayland-protocols\",\"req\":\"^0.32.9\"},{\"features\":[\"server\",\"staging\"],\"kind\":\"dev\",\"name\":\"wayland-protocols\",\"req\":\"^0.32.9\"},{\"features\":[\"client\"],\"name\":\"wayland-protocols-wlr\",\"req\":\"^0.3.9\"},{\"features\":[\"server\"],\"kind\":\"dev\",\"name\":\"wayland-protocols-wlr\",\"req\":\"^0.3.9\"},{\"kind\":\"dev\",\"name\":\"wayland-server\",\"req\":\"^0.31.10\"}],\"features\":{\"dlopen\":[\"native_lib\",\"wayland-backend/dlopen\",\"wayland-backend/dlopen\"],\"native_lib\":[\"wayland-backend/client_system\",\"wayland-backend/server_system\"]}}", "writeable_0.6.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"either\",\"optional\":true,\"req\":\"^1.9.0\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\"],\"either\":[\"dep:either\"]}}", - "x11rb-protocol_0.13.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"all-extensions\":[\"composite\",\"damage\",\"dbe\",\"dpms\",\"dri2\",\"dri3\",\"glx\",\"present\",\"randr\",\"record\",\"render\",\"res\",\"screensaver\",\"shape\",\"shm\",\"sync\",\"xevie\",\"xf86dri\",\"xf86vidmode\",\"xfixes\",\"xinerama\",\"xinput\",\"xkb\",\"xprint\",\"xselinux\",\"xtest\",\"xv\",\"xvmc\"],\"composite\":[\"xfixes\"],\"damage\":[\"xfixes\"],\"dbe\":[],\"default\":[\"std\"],\"dpms\":[],\"dri2\":[],\"dri3\":[],\"extra-traits\":[],\"glx\":[],\"present\":[\"randr\",\"xfixes\",\"sync\"],\"randr\":[\"render\"],\"record\":[],\"render\":[],\"request-parsing\":[],\"res\":[],\"resource_manager\":[\"std\"],\"screensaver\":[],\"shape\":[],\"shm\":[],\"std\":[],\"sync\":[],\"xevie\":[],\"xf86dri\":[],\"xf86vidmode\":[],\"xfixes\":[\"render\",\"shape\"],\"xinerama\":[],\"xinput\":[\"xfixes\"],\"xkb\":[],\"xprint\":[],\"xselinux\":[],\"xtest\":[],\"xv\":[\"shm\"],\"xvmc\":[\"xv\"]}}", - "x11rb_0.13.1": "{\"dependencies\":[{\"name\":\"as-raw-xcb-connection\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"gethostname\",\"req\":\"^0.4\",\"target\":\"cfg(not(unix))\"},{\"kind\":\"dev\",\"name\":\"gethostname\",\"req\":\"^0.4\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.19\"},{\"kind\":\"dev\",\"name\":\"polling\",\"req\":\"^3.4\"},{\"default_features\":false,\"features\":[\"std\",\"event\",\"fs\",\"net\",\"system\"],\"name\":\"rustix\",\"req\":\"^0.38\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"x11rb-protocol\",\"req\":\"^0.13.1\"}],\"features\":{\"all-extensions\":[\"x11rb-protocol/all-extensions\",\"composite\",\"damage\",\"dbe\",\"dpms\",\"dri2\",\"dri3\",\"glx\",\"present\",\"randr\",\"record\",\"render\",\"res\",\"screensaver\",\"shape\",\"shm\",\"sync\",\"xevie\",\"xf86dri\",\"xf86vidmode\",\"xfixes\",\"xinerama\",\"xinput\",\"xkb\",\"xprint\",\"xselinux\",\"xtest\",\"xv\",\"xvmc\"],\"allow-unsafe-code\":[\"libc\",\"as-raw-xcb-connection\"],\"composite\":[\"x11rb-protocol/composite\",\"xfixes\"],\"cursor\":[\"render\",\"resource_manager\"],\"damage\":[\"x11rb-protocol/damage\",\"xfixes\"],\"dbe\":[\"x11rb-protocol/dbe\"],\"dl-libxcb\":[\"allow-unsafe-code\",\"libloading\",\"once_cell\"],\"dpms\":[\"x11rb-protocol/dpms\"],\"dri2\":[\"x11rb-protocol/dri2\"],\"dri3\":[\"x11rb-protocol/dri3\"],\"extra-traits\":[\"x11rb-protocol/extra-traits\"],\"glx\":[\"x11rb-protocol/glx\"],\"image\":[],\"present\":[\"x11rb-protocol/present\",\"randr\",\"xfixes\",\"sync\"],\"randr\":[\"x11rb-protocol/randr\",\"render\"],\"record\":[\"x11rb-protocol/record\"],\"render\":[\"x11rb-protocol/render\"],\"request-parsing\":[\"x11rb-protocol/request-parsing\"],\"res\":[\"x11rb-protocol/res\"],\"resource_manager\":[\"x11rb-protocol/resource_manager\"],\"screensaver\":[\"x11rb-protocol/screensaver\"],\"shape\":[\"x11rb-protocol/shape\"],\"shm\":[\"x11rb-protocol/shm\"],\"sync\":[\"x11rb-protocol/sync\"],\"xevie\":[\"x11rb-protocol/xevie\"],\"xf86dri\":[\"x11rb-protocol/xf86dri\"],\"xf86vidmode\":[\"x11rb-protocol/xf86vidmode\"],\"xfixes\":[\"x11rb-protocol/xfixes\",\"render\",\"shape\"],\"xinerama\":[\"x11rb-protocol/xinerama\"],\"xinput\":[\"x11rb-protocol/xinput\",\"xfixes\"],\"xkb\":[\"x11rb-protocol/xkb\"],\"xprint\":[\"x11rb-protocol/xprint\"],\"xselinux\":[\"x11rb-protocol/xselinux\"],\"xtest\":[\"x11rb-protocol/xtest\"],\"xv\":[\"x11rb-protocol/xv\",\"shm\"],\"xvmc\":[\"x11rb-protocol/xvmc\",\"xv\"]}}", + "x11rb-protocol_0.13.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"all-extensions\":[\"composite\",\"damage\",\"dbe\",\"dpms\",\"dri2\",\"dri3\",\"glx\",\"present\",\"randr\",\"record\",\"render\",\"res\",\"screensaver\",\"shape\",\"shm\",\"sync\",\"xevie\",\"xf86dri\",\"xf86vidmode\",\"xfixes\",\"xinerama\",\"xinput\",\"xkb\",\"xprint\",\"xselinux\",\"xtest\",\"xv\",\"xvmc\"],\"composite\":[\"xfixes\"],\"damage\":[\"xfixes\"],\"dbe\":[],\"default\":[\"std\"],\"dpms\":[],\"dri2\":[],\"dri3\":[],\"extra-traits\":[],\"glx\":[],\"present\":[\"randr\",\"xfixes\",\"sync\",\"dri3\"],\"randr\":[\"render\"],\"record\":[],\"render\":[],\"request-parsing\":[],\"res\":[],\"resource_manager\":[\"std\"],\"screensaver\":[],\"shape\":[],\"shm\":[],\"std\":[],\"sync\":[],\"xevie\":[],\"xf86dri\":[],\"xf86vidmode\":[],\"xfixes\":[\"render\",\"shape\"],\"xinerama\":[],\"xinput\":[\"xfixes\"],\"xkb\":[],\"xprint\":[],\"xselinux\":[],\"xtest\":[],\"xv\":[\"shm\"],\"xvmc\":[\"xv\"]}}", + "x11rb_0.13.2": "{\"dependencies\":[{\"name\":\"as-raw-xcb-connection\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"gethostname\",\"req\":\"^1.0\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.19\"},{\"kind\":\"dev\",\"name\":\"polling\",\"req\":\"^3.4\"},{\"name\":\"raw-window-handle\",\"optional\":true,\"req\":\"^0.5.0\"},{\"default_features\":false,\"features\":[\"std\",\"event\",\"fs\",\"net\",\"system\"],\"name\":\"rustix\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"x11rb-protocol\",\"req\":\"^0.13.2\"},{\"name\":\"xcursor\",\"optional\":true,\"req\":\"^0.3.7\"}],\"features\":{\"all-extensions\":[\"x11rb-protocol/all-extensions\",\"composite\",\"damage\",\"dbe\",\"dpms\",\"dri2\",\"dri3\",\"glx\",\"present\",\"randr\",\"record\",\"render\",\"res\",\"screensaver\",\"shape\",\"shm\",\"sync\",\"xevie\",\"xf86dri\",\"xf86vidmode\",\"xfixes\",\"xinerama\",\"xinput\",\"xkb\",\"xprint\",\"xselinux\",\"xtest\",\"xv\",\"xvmc\"],\"allow-unsafe-code\":[\"libc\",\"as-raw-xcb-connection\"],\"composite\":[\"x11rb-protocol/composite\",\"xfixes\"],\"cursor\":[\"render\",\"resource_manager\",\"xcursor\"],\"damage\":[\"x11rb-protocol/damage\",\"xfixes\"],\"dbe\":[\"x11rb-protocol/dbe\"],\"dl-libxcb\":[\"allow-unsafe-code\",\"libloading\",\"once_cell\"],\"dpms\":[\"x11rb-protocol/dpms\"],\"dri2\":[\"x11rb-protocol/dri2\"],\"dri3\":[\"x11rb-protocol/dri3\"],\"extra-traits\":[\"x11rb-protocol/extra-traits\"],\"glx\":[\"x11rb-protocol/glx\"],\"image\":[],\"present\":[\"x11rb-protocol/present\",\"randr\",\"xfixes\",\"sync\"],\"randr\":[\"x11rb-protocol/randr\",\"render\"],\"record\":[\"x11rb-protocol/record\"],\"render\":[\"x11rb-protocol/render\"],\"request-parsing\":[\"x11rb-protocol/request-parsing\"],\"res\":[\"x11rb-protocol/res\"],\"resource_manager\":[\"x11rb-protocol/resource_manager\"],\"screensaver\":[\"x11rb-protocol/screensaver\"],\"shape\":[\"x11rb-protocol/shape\"],\"shm\":[\"x11rb-protocol/shm\"],\"sync\":[\"x11rb-protocol/sync\"],\"xevie\":[\"x11rb-protocol/xevie\"],\"xf86dri\":[\"x11rb-protocol/xf86dri\"],\"xf86vidmode\":[\"x11rb-protocol/xf86vidmode\"],\"xfixes\":[\"x11rb-protocol/xfixes\",\"render\",\"shape\"],\"xinerama\":[\"x11rb-protocol/xinerama\"],\"xinput\":[\"x11rb-protocol/xinput\",\"xfixes\"],\"xkb\":[\"x11rb-protocol/xkb\"],\"xprint\":[\"x11rb-protocol/xprint\"],\"xselinux\":[\"x11rb-protocol/xselinux\"],\"xtest\":[\"x11rb-protocol/xtest\"],\"xv\":[\"x11rb-protocol/xv\",\"shm\"],\"xvmc\":[\"x11rb-protocol/xvmc\",\"xv\"]}}", + "x25519-dalek_2.0.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"curve25519-dalek\",\"req\":\"^4\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"default_features\":false,\"features\":[\"getrandom\"],\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"curve25519-dalek/alloc\",\"serde?/alloc\",\"zeroize?/alloc\"],\"default\":[\"alloc\",\"precomputed-tables\",\"zeroize\"],\"getrandom\":[\"rand_core/getrandom\"],\"precomputed-tables\":[\"curve25519-dalek/precomputed-tables\"],\"reusable_secrets\":[],\"serde\":[\"dep:serde\",\"curve25519-dalek/serde\"],\"static_secrets\":[],\"zeroize\":[\"dep:zeroize\",\"curve25519-dalek/zeroize\"]}}", "xdg-home_1.3.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "xz2_0.1.7": "{\"dependencies\":[{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.1.26\"},{\"name\":\"lzma-sys\",\"req\":\"^0.1.18\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"tokio-core\",\"req\":\"^0.1.17\"},{\"name\":\"tokio-io\",\"optional\":true,\"req\":\"^0.1.12\"}],\"features\":{\"static\":[\"lzma-sys/static\"],\"tokio\":[\"tokio-io\",\"futures\"]}}", "yansi_1.0.1": "{\"dependencies\":[{\"name\":\"is-terminal\",\"optional\":true,\"req\":\"^0.4.11\"}],\"features\":{\"_nightly\":[],\"alloc\":[],\"default\":[\"std\"],\"detect-env\":[\"std\"],\"detect-tty\":[\"is-terminal\",\"std\"],\"hyperlink\":[\"std\"],\"std\":[\"alloc\"]}}", - "yoke-derive_0.8.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"features\":[\"fold\"],\"name\":\"syn\",\"req\":\"^2.0.21\"},{\"name\":\"synstructure\",\"req\":\"^0.13.0\"}],\"features\":{}}", - "yoke_0.8.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.110\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.110\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"req\":\"^1.2.0\"},{\"default_features\":false,\"name\":\"yoke-derive\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"optional\":true,\"req\":\"^0.1.3\"}],\"features\":{\"alloc\":[\"stable_deref_trait/alloc\",\"serde?/alloc\",\"zerofrom/alloc\"],\"default\":[\"alloc\",\"zerofrom\"],\"derive\":[\"dep:yoke-derive\",\"zerofrom/derive\"],\"serde\":[\"dep:serde\"],\"zerofrom\":[\"dep:zerofrom\"]}}", + "yoke-derive_0.8.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"features\":[\"fold\"],\"name\":\"syn\",\"req\":\"^2.0.21\"},{\"name\":\"synstructure\",\"req\":\"^0.13.0\"}],\"features\":{}}", + "yoke_0.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"req\":\"^1.2.0\"},{\"default_features\":false,\"name\":\"yoke-derive\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"optional\":true,\"req\":\"^0.1.3\"}],\"features\":{\"alloc\":[\"stable_deref_trait/alloc\",\"zerofrom/alloc\"],\"default\":[\"alloc\",\"zerofrom\"],\"derive\":[\"dep:yoke-derive\",\"zerofrom/derive\"],\"serde\":[],\"zerofrom\":[\"dep:zerofrom\"]}}", "zbus_4.4.0": "{\"dependencies\":[{\"name\":\"async-broadcast\",\"req\":\"^0.7.0\"},{\"name\":\"async-executor\",\"optional\":true,\"req\":\"^1.11.0\"},{\"name\":\"async-fs\",\"optional\":true,\"req\":\"^2.1.2\"},{\"name\":\"async-io\",\"optional\":true,\"req\":\"^2.3.2\"},{\"name\":\"async-lock\",\"optional\":true,\"req\":\"^3.3.0\"},{\"name\":\"async-process\",\"req\":\"^2.2.2\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"name\":\"async-recursion\",\"req\":\"^1.1.1\",\"target\":\"cfg(any(target_os = \\\"macos\\\", windows))\"},{\"name\":\"async-task\",\"optional\":true,\"req\":\"^4.7.1\"},{\"name\":\"async-trait\",\"req\":\"^0.1.80\"},{\"name\":\"blocking\",\"optional\":true,\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.3\"},{\"features\":[\"serde\"],\"name\":\"enumflags2\",\"req\":\"^0.7.9\"},{\"name\":\"event-listener\",\"req\":\"^5.3.0\"},{\"name\":\"futures-core\",\"req\":\"^0.3.30\"},{\"name\":\"futures-sink\",\"req\":\"^0.3.30\"},{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"req\":\"^0.3.30\"},{\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.30\"},{\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"default_features\":false,\"features\":[\"socket\",\"uio\",\"user\"],\"name\":\"nix\",\"req\":\"^0.29\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"ntest\",\"req\":\"^0.9.2\"},{\"name\":\"ordered-stream\",\"req\":\"^0.2\"},{\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.200\"},{\"name\":\"serde_repr\",\"req\":\"^0.1.19\"},{\"features\":[\"std\"],\"name\":\"sha1\",\"req\":\"^0.10.6\"},{\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"},{\"default_features\":false,\"features\":[\"trace\"],\"kind\":\"dev\",\"name\":\"test-log\",\"req\":\"^0.2.16\"},{\"features\":[\"rt\",\"net\",\"time\",\"fs\",\"io-util\",\"process\",\"sync\",\"tracing\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1.37.0\"},{\"features\":[\"macros\",\"rt-multi-thread\",\"fs\",\"io-util\",\"net\",\"sync\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.37.0\"},{\"name\":\"tokio-vsock\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"tracing\",\"req\":\"^0.1.40\"},{\"default_features\":false,\"features\":[\"env-filter\",\"fmt\",\"ansi\"],\"kind\":\"dev\",\"name\":\"tracing-subscriber\",\"req\":\"^0.3.18\"},{\"name\":\"uds_windows\",\"req\":\"^1.1.0\",\"target\":\"cfg(windows)\"},{\"name\":\"vsock\",\"optional\":true,\"req\":\"^0.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Security_Authorization\",\"Win32_System_Memory\",\"Win32_Networking\",\"Win32_Networking_WinSock\",\"Win32_NetworkManagement\",\"Win32_NetworkManagement_IpHelper\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(windows)\"},{\"name\":\"xdg-home\",\"req\":\"^1.1.0\"},{\"name\":\"zbus_macros\",\"req\":\"=4.4.0\"},{\"name\":\"zbus_names\",\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"zbus_xml\",\"req\":\"^4.0.0\"},{\"default_features\":false,\"features\":[\"enumflags2\"],\"name\":\"zvariant\",\"req\":\"^4.2.0\"}],\"features\":{\"async-io\":[\"dep:async-io\",\"async-executor\",\"async-task\",\"async-lock\",\"async-fs\",\"blocking\",\"futures-util/io\"],\"bus-impl\":[\"p2p\"],\"chrono\":[\"zvariant/chrono\"],\"default\":[\"async-io\"],\"heapless\":[\"zvariant/heapless\"],\"option-as-array\":[\"zvariant/option-as-array\"],\"p2p\":[],\"time\":[\"zvariant/time\"],\"tokio\":[\"dep:tokio\"],\"tokio-vsock\":[\"dep:tokio-vsock\",\"tokio\"],\"url\":[\"zvariant/url\"],\"uuid\":[\"zvariant/uuid\"],\"vsock\":[\"dep:vsock\",\"dep:async-io\"]}}", "zbus_macros_4.4.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"async-io\",\"req\":\"^2.3.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.30\"},{\"name\":\"proc-macro-crate\",\"req\":\"^3.1.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.81\"},{\"name\":\"quote\",\"req\":\"^1.0.36\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.15\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.200\"},{\"features\":[\"extra-traits\",\"fold\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0.64\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.93\"},{\"name\":\"zvariant_utils\",\"req\":\"=2.1.0\"}],\"features\":{}}", "zbus_names_3.0.0": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"features\":[\"enumflags2\"],\"name\":\"zvariant\",\"req\":\"^4.0.0\"}],\"features\":{}}", - "zerocopy-derive_0.8.26": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"dissimilar\",\"req\":\"^1.0.9\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"=0.2.163\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"=1.9\"},{\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"=0.2.17\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.1\"},{\"name\":\"quote\",\"req\":\"^1.0.10\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.89\"}],\"features\":{}}", - "zerocopy_0.8.26": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"either\",\"req\":\"=1.13.0\"},{\"kind\":\"dev\",\"name\":\"elain\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.89\"},{\"name\":\"zerocopy-derive\",\"req\":\"=0.8.26\",\"target\":\"cfg(any())\"},{\"name\":\"zerocopy-derive\",\"optional\":true,\"req\":\"=0.8.26\"},{\"kind\":\"dev\",\"name\":\"zerocopy-derive\",\"req\":\"=0.8.26\"}],\"features\":{\"__internal_use_only_features_that_work_on_stable\":[\"alloc\",\"derive\",\"simd\",\"std\"],\"alloc\":[],\"derive\":[\"zerocopy-derive\"],\"float-nightly\":[],\"simd\":[],\"simd-nightly\":[\"simd\"],\"std\":[\"alloc\"]}}", + "zerocopy-derive_0.8.37": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"dissimilar\",\"req\":\"^1.0.9\"},{\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"=0.2.17\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"proc-macro2\",\"req\":\"=1.0.80\"},{\"name\":\"quote\",\"req\":\"^1.0.40\"},{\"kind\":\"dev\",\"name\":\"quote\",\"req\":\"=1.0.40\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"visit\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.89\"}],\"features\":{}}", + "zerocopy_0.8.37": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"elain\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"=1.0.89\"},{\"name\":\"zerocopy-derive\",\"req\":\"=0.8.37\",\"target\":\"cfg(any())\"},{\"name\":\"zerocopy-derive\",\"optional\":true,\"req\":\"=0.8.37\"},{\"kind\":\"dev\",\"name\":\"zerocopy-derive\",\"req\":\"=0.8.37\"}],\"features\":{\"__internal_use_only_features_that_work_on_stable\":[\"alloc\",\"derive\",\"simd\",\"std\"],\"alloc\":[],\"derive\":[\"zerocopy-derive\"],\"float-nightly\":[],\"simd\":[],\"simd-nightly\":[\"simd\"],\"std\":[\"alloc\"]}}", "zerofrom-derive_0.1.6": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"features\":[\"fold\"],\"name\":\"syn\",\"req\":\"^2.0.21\"},{\"name\":\"synstructure\",\"req\":\"^0.13.0\"}],\"features\":{}}", "zerofrom_0.1.6": "{\"dependencies\":[{\"default_features\":false,\"name\":\"zerofrom-derive\",\"optional\":true,\"req\":\"^0.1.3\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\"],\"derive\":[\"dep:zerofrom-derive\"]}}", "zeroize_1.8.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"zeroize_derive\",\"optional\":true,\"req\":\"^1.3\"}],\"features\":{\"aarch64\":[],\"alloc\":[],\"default\":[\"alloc\"],\"derive\":[\"zeroize_derive\"],\"simd\":[],\"std\":[\"alloc\"]}}", - "zeroize_derive_1.4.2": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\",\"extra-traits\",\"visit\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", - "zerotrie_0.2.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"icu_locale_core\",\"req\":\"^2.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"litemap\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"postcard\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand_pcg\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rmp-serde\",\"req\":\"^1.2.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.110\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.110\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"optional\":true,\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\",\"zerovec?/databake\"],\"default\":[],\"litemap\":[\"dep:litemap\",\"alloc\"],\"serde\":[\"dep:serde\",\"dep:litemap\",\"alloc\",\"litemap/serde\",\"zerovec?/serde\"],\"yoke\":[\"dep:yoke\"],\"zerofrom\":[\"dep:zerofrom\"]}}", - "zerovec-derive_0.11.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.110\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.21\"}],\"features\":{}}", + "zeroize_derive_1.4.3": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\",\"extra-traits\",\"visit\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", + "zerotrie_0.2.3": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"litemap\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"optional\":true,\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\",\"zerovec?/databake\"],\"default\":[],\"litemap\":[\"dep:litemap\",\"alloc\"],\"serde\":[\"dep:serde_core\",\"dep:litemap\",\"alloc\",\"litemap/serde\",\"zerovec?/serde\"],\"yoke\":[\"dep:yoke\"],\"zerofrom\":[\"dep:zerofrom\"]}}", + "zerovec-derive_0.11.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.21\"}],\"features\":{}}", "zerovec_0.11.5": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"xxhash64\"],\"name\":\"twox-hash\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec-derive\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"databake\":[\"dep:databake\"],\"derive\":[\"dep:zerovec-derive\"],\"hashmap\":[\"dep:twox-hash\",\"alloc\"],\"serde\":[\"dep:serde\"],\"std\":[],\"yoke\":[\"dep:yoke\"]}}", + "zip_2.4.2": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"req\":\"^1.4.1\",\"target\":\"cfg(fuzzing)\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"=4.4.18\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"crc32fast\",\"req\":\"^1.4\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"name\":\"deflate64\",\"optional\":true,\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"wasm_js\",\"std\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.1\"},{\"features\":[\"wasm_js\",\"std\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3.1\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"lzma-rs\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"memchr\",\"req\":\"^2.7\"},{\"default_features\":false,\"name\":\"nt-time\",\"optional\":true,\"req\":\"^0.10.6\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.15\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.37\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.37\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5\"},{\"name\":\"xz2\",\"optional\":true,\"req\":\"^0.1.7\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.8\"},{\"name\":\"zopfli\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"}],\"features\":{\"_all-features\":[],\"_deflate-any\":[],\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\",\"getrandom\",\"zeroize\"],\"chrono\":[\"chrono/default\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate64\",\"deflate\",\"lzma\",\"time\",\"zstd\",\"xz\"],\"deflate\":[\"flate2/rust_backend\",\"deflate-zopfli\",\"deflate-flate2\"],\"deflate-flate2\":[\"_deflate-any\"],\"deflate-miniz\":[\"deflate\",\"deflate-flate2\"],\"deflate-zlib\":[\"flate2/zlib\",\"deflate-flate2\"],\"deflate-zlib-ng\":[\"flate2/zlib-ng\",\"deflate-flate2\"],\"deflate-zopfli\":[\"zopfli\",\"_deflate-any\"],\"lzma\":[\"lzma-rs/stream\"],\"nt-time\":[\"dep:nt-time\"],\"unreserved\":[],\"xz\":[\"dep:xz2\"]}}", + "zmij_1.0.19": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", + "zopfli_0.8.3": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.19.0\"},{\"default_features\":false,\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.5.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.28\"},{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"simd-adler32\",\"optional\":true,\"req\":\"^0.3.7\"}],\"features\":{\"default\":[\"gzip\",\"std\",\"zlib\"],\"gzip\":[\"dep:crc32fast\"],\"nightly\":[\"crc32fast?/nightly\"],\"std\":[\"crc32fast?/std\",\"dep:log\",\"simd-adler32?/std\"],\"zlib\":[\"dep:simd-adler32\"]}}", "zstd-safe_7.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"zstd-sys\",\"req\":\"^2.0.15\"}],\"features\":{\"arrays\":[],\"bindgen\":[\"zstd-sys/bindgen\"],\"debug\":[\"zstd-sys/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-sys/experimental\"],\"fat-lto\":[\"zstd-sys/fat-lto\"],\"legacy\":[\"zstd-sys/legacy\"],\"no_asm\":[\"zstd-sys/no_asm\"],\"pkg-config\":[\"zstd-sys/pkg-config\"],\"seekable\":[\"zstd-sys/seekable\"],\"std\":[\"zstd-sys/std\"],\"thin\":[\"zstd-sys/thin\"],\"thin-lto\":[\"zstd-sys/thin-lto\"],\"zdict_builder\":[\"zstd-sys/zdict_builder\"],\"zstdmt\":[\"zstd-sys/zstdmt\"]}}", "zstd-sys_2.0.16+zstd.1.5.7": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72\"},{\"features\":[\"parallel\"],\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.45\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.28\"}],\"features\":{\"debug\":[],\"default\":[\"legacy\",\"zdict_builder\",\"bindgen\"],\"experimental\":[],\"fat-lto\":[],\"legacy\":[],\"no_asm\":[],\"no_wasm_shim\":[],\"non-cargo\":[],\"pkg-config\":[],\"seekable\":[],\"std\":[],\"thin\":[],\"thin-lto\":[],\"zdict_builder\":[],\"zstdmt\":[]}}", "zstd_0.13.3": "{\"dependencies\":[{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.0\"},{\"kind\":\"dev\",\"name\":\"humansize\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"zstd-safe\",\"req\":\"^7.1.0\"}],\"features\":{\"arrays\":[\"zstd-safe/arrays\"],\"bindgen\":[\"zstd-safe/bindgen\"],\"debug\":[\"zstd-safe/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-safe/experimental\"],\"fat-lto\":[\"zstd-safe/fat-lto\"],\"legacy\":[\"zstd-safe/legacy\"],\"no_asm\":[\"zstd-safe/no_asm\"],\"pkg-config\":[\"zstd-safe/pkg-config\"],\"thin\":[\"zstd-safe/thin\"],\"thin-lto\":[\"zstd-safe/thin-lto\"],\"wasm\":[],\"zdict_builder\":[\"zstd-safe/zdict_builder\"],\"zstdmt\":[\"zstd-safe/zstdmt\"]}}", "zune-core_0.4.12": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.52\"}],\"features\":{\"std\":[]}}", - "zune-core_0.5.0": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"std\":[]}}", - "zune-jpeg_0.4.19": "{\"dependencies\":[{\"name\":\"zune-core\",\"req\":\"^0.4\"}],\"features\":{\"default\":[\"x86\",\"neon\",\"std\"],\"log\":[\"zune-core/log\"],\"neon\":[],\"std\":[\"zune-core/std\"],\"x86\":[]}}", - "zune-jpeg_0.5.5": "{\"dependencies\":[{\"name\":\"zune-core\",\"req\":\"^0.5\"}],\"features\":{\"default\":[\"x86\",\"neon\",\"std\"],\"log\":[\"zune-core/log\"],\"neon\":[],\"std\":[\"zune-core/std\"],\"x86\":[]}}", + "zune-core_0.5.1": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"std\":[]}}", + "zune-jpeg_0.4.21": "{\"dependencies\":[{\"name\":\"zune-core\",\"req\":\"^0.4\"}],\"features\":{\"default\":[\"x86\",\"neon\",\"std\"],\"log\":[\"zune-core/log\"],\"neon\":[],\"std\":[\"zune-core/std\"],\"x86\":[]}}", + "zune-jpeg_0.5.12": "{\"dependencies\":[{\"name\":\"zune-core\",\"req\":\"^0.5.1\"}],\"features\":{\"default\":[\"x86\",\"neon\",\"std\"],\"log\":[\"zune-core/log\"],\"neon\":[],\"portable_simd\":[],\"std\":[\"zune-core/std\"],\"x86\":[]}}", "zvariant_4.2.0": "{\"dependencies\":[{\"features\":[\"serde\"],\"name\":\"arrayvec\",\"optional\":true,\"req\":\"^0.7.4\"},{\"default_features\":false,\"features\":[\"serde\"],\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"endi\",\"req\":\"^1.1.0\"},{\"features\":[\"serde\"],\"name\":\"enumflags2\",\"optional\":true,\"req\":\"^0.7.9\"},{\"kind\":\"dev\",\"name\":\"glib\",\"req\":\"^0.20.0\"},{\"features\":[\"serde\"],\"name\":\"heapless\",\"optional\":true,\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.200\"},{\"name\":\"serde_bytes\",\"optional\":true,\"req\":\"^0.11.14\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.116\"},{\"kind\":\"dev\",\"name\":\"serde_repr\",\"req\":\"^0.1.19\"},{\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"features\":[\"serde\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.36\"},{\"features\":[\"serde\"],\"name\":\"url\",\"optional\":true,\"req\":\"^2.5.0\"},{\"features\":[\"serde\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"^1.8.0\"},{\"name\":\"zvariant_derive\",\"req\":\"=4.2.0\"}],\"features\":{\"default\":[],\"gvariant\":[],\"option-as-array\":[],\"ostree-tests\":[\"gvariant\"]}}", "zvariant_derive_4.2.0": "{\"dependencies\":[{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"enumflags2\",\"req\":\"^0.7.9\"},{\"name\":\"proc-macro-crate\",\"req\":\"^3.1.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.81\"},{\"name\":\"quote\",\"req\":\"^1.0.36\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.200\"},{\"kind\":\"dev\",\"name\":\"serde_repr\",\"req\":\"^0.1.19\"},{\"features\":[\"extra-traits\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0.64\"},{\"name\":\"zvariant_utils\",\"req\":\"=2.1.0\"}],\"features\":{}}", "zvariant_utils_2.1.0": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.81\"},{\"name\":\"quote\",\"req\":\"^1.0.36\"},{\"features\":[\"extra-traits\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0.64\"}],\"features\":{}}" diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index f29403f68c..7eac572443 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1867,7 +1867,10 @@ impl ChatWidget { /// Runs a commit tick for the current stream queue snapshot. /// /// `scope` controls whether this call may commit in smooth mode or only when catch-up - /// is currently active. + /// is currently active. While lines are actively streaming we hide the status row to avoid + /// duplicate "in progress" affordances, but once all stream controllers go idle for this + /// turn we restore the status row if the task is still running so users keep a live + /// spinner/shimmer signal between preamble output and subsequent tool activity. fn run_commit_tick_with_scope(&mut self, scope: CommitTickScope) { let now = Instant::now(); let outcome = run_commit_tick( @@ -1883,6 +1886,10 @@ impl ChatWidget { } if outcome.has_controller && outcome.all_idle { + if self.bottom_pane.is_task_running() { + self.bottom_pane.ensure_status_indicator(); + self.set_status_header(self.current_status_header.clone()); + } self.app_event_tx.send(AppEvent::StopCommitAnimation); } } diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__preamble_keeps_working_status.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__preamble_keeps_working_status.snap new file mode 100644 index 0000000000..b240e4b5f6 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__preamble_keeps_working_status.snap @@ -0,0 +1,11 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: terminal.backend() +--- +" " +"• Working (0s • esc to interrupt) " +" " +" " +"› Ask Codex to do anything " +" " +" ? for shortcuts 100% context left " diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index dba8bf82e1..35c365e253 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1810,7 +1810,7 @@ async fn streaming_final_answer_keeps_task_running_state() { chat.on_commit_tick(); assert!(chat.bottom_pane.is_task_running()); - assert!(chat.bottom_pane.status_widget().is_none()); + assert!(chat.bottom_pane.status_widget().is_some()); chat.bottom_pane .set_composer_text("queued submission".to_string(), Vec::new(), Vec::new()); @@ -1832,7 +1832,7 @@ async fn streaming_final_answer_keeps_task_running_state() { } #[tokio::test] -async fn exec_begin_restores_status_indicator_after_preamble() { +async fn preamble_keeps_status_indicator_visible_until_exec_begin() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; chat.on_task_started(); @@ -1842,7 +1842,7 @@ async fn exec_begin_restores_status_indicator_after_preamble() { chat.on_commit_tick(); drain_insert_history(&mut rx); - assert_eq!(chat.bottom_pane.status_indicator_visible(), false); + assert_eq!(chat.bottom_pane.status_indicator_visible(), true); assert_eq!(chat.bottom_pane.is_task_running(), true); begin_exec(&mut chat, "call-1", "echo hi"); @@ -1850,6 +1850,27 @@ async fn exec_begin_restores_status_indicator_after_preamble() { assert_eq!(chat.bottom_pane.status_indicator_visible(), true); } +#[tokio::test] +async fn preamble_keeps_working_status_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + chat.thread_id = Some(ThreadId::new()); + + // Regression sequence: a preamble line is committed to history before any exec/tool event. + // The status row must remain visible so the spinner/shimmer still communicates "working". + chat.on_task_started(); + chat.on_agent_message_delta("Preamble line\n".to_string()); + chat.on_commit_tick(); + drain_insert_history(&mut rx); + + let height = chat.desired_height(80); + let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(80, height)) + .expect("create terminal"); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw preamble + status widget"); + assert_snapshot!("preamble_keeps_working_status", terminal.backend()); +} + #[tokio::test] async fn ctrl_c_shutdown_works_with_caps_lock() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; From 4ed8d74aab66f261fb08c4ad55fda936ff92e533 Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Wed, 4 Feb 2026 19:56:50 -0800 Subject: [PATCH 43/90] fix: ensure status indicator present earlier in exec path (#10700) ensure status indicator present in all classifications of exec tool. fixes indicator disappearing after preambles, will look into using `phase` to avoid this class of error in a few hours. commands parsed as unknown faced this issue tested locally, added test for specific failure flow --- codex-rs/tui/src/chatwidget.rs | 2 + ...ed_exec_begin_restores_working_status.snap | 12 ++++++ codex-rs/tui/src/chatwidget/tests.rs | 42 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__unified_exec_begin_restores_working_status.snap diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 7eac572443..4627613764 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1528,6 +1528,8 @@ impl ChatWidget { fn on_exec_command_begin(&mut self, ev: ExecCommandBeginEvent) { self.flush_answer_stream_with_separator(); if is_unified_exec_source(ev.source) { + // Unified exec may be parsed as Unknown; keep the working indicator visible regardless. + self.bottom_pane.ensure_status_indicator(); self.track_unified_exec_process_begin(&ev); if !is_standard_tool_call(&ev.parsed_cmd) { return; diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__unified_exec_begin_restores_working_status.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__unified_exec_begin_restores_working_status.snap new file mode 100644 index 0000000000..a2ab0f168a --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__unified_exec_begin_restores_working_status.snap @@ -0,0 +1,12 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: terminal.backend() +--- +" " +"• Working (0s • esc to interrupt) " +" 1 background terminal running · /ps to view " +" " +" " +"› Ask Codex to do anything " +" " +" ? for shortcuts 100% context left " diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 35c365e253..ba28160099 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1871,6 +1871,48 @@ async fn preamble_keeps_working_status_snapshot() { assert_snapshot!("preamble_keeps_working_status", terminal.backend()); } +#[tokio::test] +async fn unified_exec_begin_restores_status_indicator_after_preamble() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; + + chat.on_task_started(); + assert_eq!(chat.bottom_pane.status_indicator_visible(), true); + + // Simulate a hidden status row during an active turn. + chat.bottom_pane.hide_status_indicator(); + assert_eq!(chat.bottom_pane.status_indicator_visible(), false); + assert_eq!(chat.bottom_pane.is_task_running(), true); + + begin_unified_exec_startup(&mut chat, "call-1", "proc-1", "sleep 2"); + + assert_eq!(chat.bottom_pane.status_indicator_visible(), true); +} + +#[tokio::test] +async fn unified_exec_begin_restores_working_status_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + + chat.on_task_started(); + chat.on_agent_message_delta("Preamble line\n".to_string()); + chat.on_commit_tick(); + drain_insert_history(&mut rx); + + begin_unified_exec_startup(&mut chat, "call-1", "proc-1", "sleep 2"); + + let width: u16 = 80; + let height = chat.desired_height(width); + let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(width, height)) + .expect("create terminal"); + terminal.set_viewport_area(Rect::new(0, 0, width, height)); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw chatwidget"); + assert_snapshot!( + "unified_exec_begin_restores_working_status", + terminal.backend() + ); +} + #[tokio::test] async fn ctrl_c_shutdown_works_with_caps_lock() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; From 1dc06b6ffcc8e9d90511f721d23dd1f61619b06c Mon Sep 17 00:00:00 2001 From: cryptonerdcn Date: Thu, 5 Feb 2026 14:19:56 +0900 Subject: [PATCH 44/90] fix: ensure resume args precede image args (#10709) ## Summary Fixes argument ordering when `resumeThread()` is used with `local_image`. The SDK previously emitted CLI args with `--image` before `resume `, which caused the Codex CLI to treat `resume`/UUID as image paths and start a new session. This PR moves `resume ` before any `--image` flags and adds a regression test. ## Bug Report / Links - OpenAI issue: https://github.com/openai/codex/issues/10708 - Repro repo: https://github.com/cryptonerdcn/codex-resume-local-image-repro - Repro issue (repo): https://github.com/cryptonerdcn/codex-resume-local-image-repro/issues/1 ## Repro (pre-fix) 1. Build SDK from source 2. Run resume + local_image 3. Args order: `--image resume ` 4. Result: new session created (thread id changes) ## Fix Move `resume ` before `--image` in `CodexExec.run` and add a regression test to assert ordering. ## Tests - `cd sdk/typescript && npm test` - **Failed**: `codex-rs/target/debug/codex` missing (ENOENT) ## Notes - I can rerun tests in an environment with `codex-rs` built and report results. --- sdk/typescript/src/exec.ts | 8 ++++---- sdk/typescript/tests/exec.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/sdk/typescript/src/exec.ts b/sdk/typescript/src/exec.ts index d569106c84..6f8048e5e4 100644 --- a/sdk/typescript/src/exec.ts +++ b/sdk/typescript/src/exec.ts @@ -115,16 +115,16 @@ export class CodexExec { commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`); } + if (args.threadId) { + commandArgs.push("resume", args.threadId); + } + if (args.images?.length) { for (const image of args.images) { commandArgs.push("--image", image); } } - if (args.threadId) { - commandArgs.push("resume", args.threadId); - } - const env: Record = {}; if (this.envOverride) { Object.assign(env, this.envOverride); diff --git a/sdk/typescript/tests/exec.test.ts b/sdk/typescript/tests/exec.test.ts index 9c4b6c2530..7ef52d72e1 100644 --- a/sdk/typescript/tests/exec.test.ts +++ b/sdk/typescript/tests/exec.test.ts @@ -67,4 +67,30 @@ describe("CodexExec", () => { expect(result.error.message).toMatch(/Codex Exec exited/); } }); + + it("places resume args before image args", async () => { + const { CodexExec } = await import("../src/exec"); + spawnMock.mockClear(); + const child = new FakeChildProcess(); + spawnMock.mockReturnValue(child as unknown as child_process.ChildProcess); + + setImmediate(() => { + child.stdout.end(); + child.stderr.end(); + child.emit("exit", 0, null); + }); + + const exec = new CodexExec("codex"); + for await (const _ of exec.run({ input: "hi", images: ["img.png"], threadId: "thread-id" })) { + // no-op + } + + const commandArgs = spawnMock.mock.calls[0]?.[1] as string[] | undefined; + expect(commandArgs).toBeDefined(); + const resumeIndex = commandArgs!.indexOf("resume"); + const imageIndex = commandArgs!.indexOf("--image"); + expect(resumeIndex).toBeGreaterThan(-1); + expect(imageIndex).toBeGreaterThan(-1); + expect(resumeIndex).toBeLessThan(imageIndex); + }); }); From a05aadfa1b2dc5cd9d19e7dc2ed9984f5b37b212 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Wed, 4 Feb 2026 21:22:47 -0800 Subject: [PATCH 45/90] chore(config) Default Personality Pragmatic (#10705) ## Summary Switch back to Pragmatic personality ## Testing - [x] Updated unit tests --- codex-rs/app-server/tests/suite/v2/thread_resume.rs | 2 +- codex-rs/app-server/tests/suite/v2/turn_start.rs | 4 ++-- codex-rs/core/src/config/mod.rs | 10 +++++----- codex-rs/core/tests/suite/personality.rs | 12 ++++++------ ...atwidget__tests__personality_selection_popup.snap | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) 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 358fec351f..28b604001d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -403,7 +403,7 @@ async fn thread_resume_accepts_personality_override() -> Result<()> { .send_thread_resume_request(ThreadResumeParams { thread_id: thread.id.clone(), model: Some("gpt-5.2-codex".to_string()), - personality: Some(Personality::Pragmatic), + personality: Some(Personality::Friendly), ..Default::default() }) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index bbf94f1bd0..87ae4cfaab 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -455,7 +455,7 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> { text: "Hello".to_string(), text_elements: Vec::new(), }], - personality: Some(Personality::Pragmatic), + personality: Some(Personality::Friendly), ..Default::default() }) .await?; @@ -560,7 +560,7 @@ async fn turn_start_change_personality_mid_thread_v2() -> Result<()> { text: "Hello again".to_string(), text_elements: Vec::new(), }], - personality: Some(Personality::Pragmatic), + personality: Some(Personality::Friendly), ..Default::default() }) .await?; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 58768e584d..94e36eb90b 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1550,7 +1550,7 @@ impl Config { .or_else(|| { features .enabled(Feature::Personality) - .then_some(Personality::Friendly) + .then_some(Personality::Pragmatic) }); let experimental_compact_prompt_path = config_profile @@ -3870,7 +3870,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: None, model_verbosity: None, - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -3956,7 +3956,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: None, model_verbosity: None, - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -4057,7 +4057,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::default(), model_supports_reasoning_summaries: None, model_verbosity: None, - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, @@ -4144,7 +4144,7 @@ model_verbosity = "high" model_reasoning_summary: ReasoningSummary::Detailed, model_supports_reasoning_summaries: None, model_verbosity: Some(Verbosity::High), - personality: Some(Personality::Friendly), + personality: Some(Personality::Pragmatic), chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(), base_instructions: None, developer_instructions: None, diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index ff2c0cfec0..d5d7a81baf 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -233,7 +233,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn default_personality_is_friendly_without_config_toml() -> anyhow::Result<()> { +async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -269,7 +269,7 @@ async fn default_personality_is_friendly_without_config_toml() -> anyhow::Result let request = resp_mock.single_request(); let instructions_text = request.instructions_text(); assert!( - instructions_text.contains(LOCAL_FRIENDLY_TEMPLATE), + instructions_text.contains(LOCAL_PRAGMATIC_TEMPLATE), "expected default friendly template, got: {instructions_text:?}" ); @@ -324,7 +324,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> effort: None, summary: None, collaboration_mode: None, - personality: Some(Personality::Pragmatic), + personality: Some(Personality::Friendly), }) .await?; @@ -365,7 +365,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()> "expected personality update preamble, got {personality_text:?}" ); assert!( - personality_text.contains(LOCAL_PRAGMATIC_TEMPLATE), + personality_text.contains(LOCAL_FRIENDLY_TEMPLATE), "expected personality update to include the local pragmatic template, got: {personality_text:?}" ); @@ -902,7 +902,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - effort: None, summary: None, collaboration_mode: None, - personality: Some(Personality::Pragmatic), + personality: Some(Personality::Friendly), }) .await?; @@ -942,7 +942,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - "expected personality update preamble, got {personality_text:?}" ); assert!( - personality_text.contains(remote_pragmatic_message), + personality_text.contains(remote_friendly_message), "expected personality update to include remote template, got: {personality_text:?}" ); diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__personality_selection_popup.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__personality_selection_popup.snap index 3c6bba94e6..3cd887f2e4 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__personality_selection_popup.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__personality_selection_popup.snap @@ -5,7 +5,7 @@ expression: popup Select Personality Choose a communication style for Codex. Disable in /experimental. -› 1. Friendly (current) Warm, collaborative, and helpful. - 2. Pragmatic Concise, task-focused, and direct. + 1. Friendly Warm, collaborative, and helpful. +› 2. Pragmatic (current) Concise, task-focused, and direct. Press enter to confirm or esc to go back From e482978261b787716859fb8c0a54022e8183cbf8 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Wed, 4 Feb 2026 21:50:38 -0800 Subject: [PATCH 46/90] fix(core) switching model appends model instructions (#10651) ## Summary When switching models, we should append the instructions of the new model to the conversation as a developer message. ## Test - [x] Adds a unit test --- codex-rs/core/src/codex.rs | 26 +++ .../tests/suite/collaboration_instructions.rs | 10 +- codex-rs/core/tests/suite/mod.rs | 1 + codex-rs/core/tests/suite/model_switching.rs | 192 ++++++++++++++++++ codex-rs/core/tests/suite/personality.rs | 4 +- codex-rs/core/tests/suite/prompt_caching.rs | 26 ++- codex-rs/protocol/src/models.rs | 6 + 7 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 codex-rs/core/tests/suite/model_switching.rs diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index cef00f64eb..dda701d69e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1460,6 +1460,9 @@ impl Session { return None; } let previous = previous?; + if next.model_info.slug != previous.model_info.slug { + return None; + } // if a personality is specified and it's different from the previous one, build a personality update item if let Some(personality) = next.personality @@ -1498,6 +1501,24 @@ impl Session { } } + fn build_model_instructions_update_item( + &self, + previous: Option<&Arc>, + next: &TurnContext, + ) -> Option { + let prev = previous?; + if prev.model_info.slug == next.model_info.slug { + return None; + } + + let model_instructions = next.model_info.get_model_instructions(next.personality); + if model_instructions.is_empty() { + return None; + } + + Some(DeveloperInstructions::model_switch_message(model_instructions).into()) + } + fn build_settings_update_items( &self, previous_context: Option<&Arc>, @@ -1519,6 +1540,11 @@ impl Session { { update_items.push(collaboration_mode_item); } + if let Some(model_instructions_item) = + self.build_model_instructions_update_item(previous_context, current_context) + { + update_items.push(model_instructions_item); + } if let Some(personality_item) = self.build_personality_update_item(previous_context, current_context) { diff --git a/codex-rs/core/tests/suite/collaboration_instructions.rs b/codex-rs/core/tests/suite/collaboration_instructions.rs index dc9905379e..160866819a 100644 --- a/codex-rs/core/tests/suite/collaboration_instructions.rs +++ b/codex-rs/core/tests/suite/collaboration_instructions.rs @@ -705,6 +705,7 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { .await; let test = test_codex().build(&server).await?; + let current_model = test.session_configured.model.clone(); test.codex .submit(Op::OverrideTurnContext { @@ -715,7 +716,14 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> { model: None, effort: None, summary: None, - collaboration_mode: Some(collab_mode_with_instructions(Some(""))), + collaboration_mode: Some(CollaborationMode { + mode: ModeKind::Default, + settings: Settings { + model: current_model, + reasoning_effort: None, + developer_instructions: Some("".to_string()), + }, + }), personality: None, }) .await?; diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 8b057c5e16..b903f4e6ca 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -84,6 +84,7 @@ mod live_cli; mod live_reload; mod model_info_overrides; mod model_overrides; +mod model_switching; mod model_tools; mod models_cache_ttl; mod models_etag_responses; diff --git a/codex-rs/core/tests/suite/model_switching.rs b/codex-rs/core/tests/suite/model_switching.rs new file mode 100644 index 0000000000..73e957bdc4 --- /dev/null +++ b/codex-rs/core/tests/suite/model_switching.rs @@ -0,0 +1,192 @@ +use anyhow::Result; +use codex_core::config::types::Personality; +use codex_core::features::Feature; +use codex_core::protocol::AskForApproval; +use codex_core::protocol::EventMsg; +use codex_core::protocol::Op; +use codex_core::protocol::SandboxPolicy; +use codex_protocol::config_types::ReasoningSummary; +use codex_protocol::user_input::UserInput; +use core_test_support::responses::mount_sse_sequence; +use core_test_support::responses::sse_completed; +use core_test_support::responses::start_mock_server; +use core_test_support::skip_if_no_network; +use core_test_support::test_codex::test_codex; +use core_test_support::wait_for_event; +use pretty_assertions::assert_eq; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn model_change_appends_model_instructions_developer_message() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let resp_mock = mount_sse_sequence( + &server, + vec![sse_completed("resp-1"), sse_completed("resp-2")], + ) + .await; + + let mut builder = test_codex().with_model("gpt-5.2-codex"); + let test = builder.build(&server).await?; + let next_model = "gpt-5.1-codex-max"; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::ReadOnly, + model: test.session_configured.model.clone(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: Some(next_model.to_string()), + effort: None, + summary: None, + collaboration_mode: None, + personality: None, + }) + .await?; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "switch models".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::ReadOnly, + model: next_model.to_string(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let requests = resp_mock.requests(); + assert_eq!(requests.len(), 2, "expected two model requests"); + + let second_request = requests.last().expect("expected second request"); + let developer_texts = second_request.message_input_texts("developer"); + let model_switch_text = developer_texts + .iter() + .find(|text| text.contains("")) + .expect("expected model switch message in developer input"); + assert!( + model_switch_text.contains("The user was previously using a different model."), + "expected model switch preamble, got: {model_switch_text:?}" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn model_and_personality_change_only_appends_model_instructions() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let resp_mock = mount_sse_sequence( + &server, + vec![sse_completed("resp-1"), sse_completed("resp-2")], + ) + .await; + + let mut builder = test_codex() + .with_model("gpt-5.2-codex") + .with_config(|config| { + config.features.enable(Feature::Personality); + }); + let test = builder.build(&server).await?; + let next_model = "exp-codex-personality"; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "hello".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::ReadOnly, + model: test.session_configured.model.clone(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + test.codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: Some(next_model.to_string()), + effort: None, + summary: None, + collaboration_mode: None, + personality: Some(Personality::Pragmatic), + }) + .await?; + + test.codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "switch model and personality".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: test.cwd_path().to_path_buf(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::ReadOnly, + model: next_model.to_string(), + effort: test.config.model_reasoning_effort, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + wait_for_event(&test.codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await; + + let requests = resp_mock.requests(); + assert_eq!(requests.len(), 2, "expected two model requests"); + + let second_request = requests.last().expect("expected second request"); + let developer_texts = second_request.message_input_texts("developer"); + assert!( + developer_texts + .iter() + .any(|text| text.contains("")), + "expected model switch message when model changes" + ); + assert!( + !developer_texts + .iter() + .any(|text| text.contains("")), + "did not expect personality update message when model changed in same turn" + ); + + Ok(()) +} diff --git a/codex-rs/core/tests/suite/personality.rs b/codex-rs/core/tests/suite/personality.rs index d5d7a81baf..4fdedd0798 100644 --- a/codex-rs/core/tests/suite/personality.rs +++ b/codex-rs/core/tests/suite/personality.rs @@ -882,7 +882,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - cwd: test.cwd_path().to_path_buf(), approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::ReadOnly, - model: test.session_configured.model.clone(), + model: remote_slug.to_string(), effort: test.config.model_reasoning_effort, summary: ReasoningSummary::Auto, collaboration_mode: None, @@ -898,7 +898,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() - approval_policy: None, sandbox_policy: None, windows_sandbox_level: None, - model: Some(remote_slug.to_string()), + model: None, effort: None, summary: None, collaboration_mode: None, diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index acff97381a..9b1548591b 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -384,7 +384,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an approval_policy: Some(AskForApproval::Never), sandbox_policy: Some(new_policy.clone()), windows_sandbox_level: None, - model: Some("o3".to_string()), + model: None, effort: Some(Some(ReasoningEffort::High)), summary: Some(ReasoningSummary::Detailed), collaboration_mode: None, @@ -676,9 +676,21 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res expected_permissions_msg_2, expected_permissions_msg, "expected updated permissions message after per-turn override" ); + let expected_model_switch_msg = body2["input"][body1_input.len() + 2].clone(); + assert_eq!( + expected_model_switch_msg["role"].as_str(), + Some("developer") + ); + assert!( + expected_model_switch_msg["content"][0]["text"] + .as_str() + .is_some_and(|text| text.contains("")), + "expected model switch message after model override: {expected_model_switch_msg:?}" + ); let mut expected_body2 = body1_input.to_vec(); expected_body2.push(expected_env_msg_2); expected_body2.push(expected_permissions_msg_2); + expected_body2.push(expected_model_switch_msg); expected_body2.push(expected_user_message_2); assert_eq!(body2["input"], serde_json::Value::Array(expected_body2)); @@ -892,6 +904,17 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu expected_permissions_msg_2, expected_permissions_msg, "expected updated permissions message after policy change" ); + let expected_model_switch_msg = body2["input"][body1_input.len() + 1].clone(); + assert_eq!( + expected_model_switch_msg["role"].as_str(), + Some("developer") + ); + assert!( + expected_model_switch_msg["content"][0]["text"] + .as_str() + .is_some_and(|text| text.contains("")), + "expected model switch message after model override: {expected_model_switch_msg:?}" + ); let expected_user_message_2 = text_user_input("hello 2".to_string()); let expected_input_2 = serde_json::Value::Array(vec![ expected_permissions_msg, @@ -899,6 +922,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu expected_env_msg_1, expected_user_message_1, expected_permissions_msg_2, + expected_model_switch_msg, expected_user_message_2, ]); assert_eq!(body2["input"], expected_input_2); diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 15114d970b..ae38a7890c 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -274,6 +274,12 @@ impl DeveloperInstructions { Self { text } } + pub fn model_switch_message(model_instructions: String) -> Self { + DeveloperInstructions::new(format!( + "\nThe user was previously using a different model. Please continue the conversation according to the following instructions:\n\n{model_instructions}\n" + )) + } + pub fn personality_spec_message(spec: String) -> Self { let message = format!( " The user has requested a new communication style. Future messages should adhere to the following personality: \n{spec} " From 41b4962b0a7f5d73bb23d329ad9bb742545f6a2c Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Wed, 4 Feb 2026 23:03:28 -0800 Subject: [PATCH 47/90] Sync collaboration mode naming across Default prompt, tools, and TUI (#10666) ## Summary - add shared `ModeKind` helpers for display names, TUI visibility, and `request_user_input` availability - derive TUI mode filtering/labels from shared `ModeKind` metadata instead of local hardcoded matches - derive `request_user_input` availability text and unavailable error mode names from shared mode metadata - replace hardcoded known mode names in the Default collaboration-mode template with `{{KNOWN_MODE_NAMES}}` and fill it from `TUI_VISIBLE_COLLABORATION_MODES` - add regression tests for mode metadata sync and placeholder replacement ## Notes - `cargo test -p codex-core` integration target (`tests/all`) still shows pre-existing env-specific failures in this environment due missing `test_stdio_server` binary resolution; core unit tests are green. ## Codex author `codex resume 019c26ff-dfe7-7173-bc04-c9e1fff1e447` --- .../collaboration_mode_presets.rs | 73 ++++++++++++++++++- .../src/tools/handlers/request_user_input.rs | 52 +++---------- .../templates/collaboration_mode/default.md | 4 +- codex-rs/protocol/src/config_types.rs | 34 +++++++++ codex-rs/tui/src/chatwidget.rs | 9 +-- codex-rs/tui/src/collaboration_modes.rs | 8 +- 6 files changed, 125 insertions(+), 55 deletions(-) diff --git a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs index 5f297b2cfa..cdc5134038 100644 --- a/codex-rs/core/src/models_manager/collaboration_mode_presets.rs +++ b/codex-rs/core/src/models_manager/collaboration_mode_presets.rs @@ -1,10 +1,13 @@ use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::ModeKind; +use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES; use codex_protocol::openai_models::ReasoningEffort; const COLLABORATION_MODE_PLAN: &str = include_str!("../../templates/collaboration_mode/plan.md"); const COLLABORATION_MODE_DEFAULT: &str = include_str!("../../templates/collaboration_mode/default.md"); +const KNOWN_MODE_NAMES_PLACEHOLDER: &str = "{{KNOWN_MODE_NAMES}}"; +const REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER: &str = "{{REQUEST_USER_INPUT_AVAILABILITY}}"; pub(super) fn builtin_collaboration_mode_presets() -> Vec { vec![plan_preset(), default_preset()] @@ -17,7 +20,7 @@ pub fn test_builtin_collaboration_mode_presets() -> Vec { fn plan_preset() -> CollaborationModeMask { CollaborationModeMask { - name: "Plan".to_string(), + name: ModeKind::Plan.display_name().to_string(), mode: Some(ModeKind::Plan), model: None, reasoning_effort: Some(Some(ReasoningEffort::Medium)), @@ -27,10 +30,74 @@ fn plan_preset() -> CollaborationModeMask { fn default_preset() -> CollaborationModeMask { CollaborationModeMask { - name: "Default".to_string(), + name: ModeKind::Default.display_name().to_string(), mode: Some(ModeKind::Default), model: None, reasoning_effort: None, - developer_instructions: Some(Some(COLLABORATION_MODE_DEFAULT.to_string())), + developer_instructions: Some(Some(default_mode_instructions())), + } +} + +fn default_mode_instructions() -> String { + let known_mode_names = format_mode_names(&TUI_VISIBLE_COLLABORATION_MODES); + let request_user_input_availability = + request_user_input_availability_message(ModeKind::Default); + COLLABORATION_MODE_DEFAULT + .replace(KNOWN_MODE_NAMES_PLACEHOLDER, &known_mode_names) + .replace( + REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER, + &request_user_input_availability, + ) +} + +fn format_mode_names(modes: &[ModeKind]) -> String { + let mode_names: Vec<&str> = modes.iter().map(|mode| mode.display_name()).collect(); + match mode_names.as_slice() { + [] => "none".to_string(), + [mode_name] => (*mode_name).to_string(), + [first, second] => format!("{first} and {second}"), + [..] => mode_names.join(", "), + } +} + +fn request_user_input_availability_message(mode: ModeKind) -> String { + let mode_name = mode.display_name(); + if mode.allows_request_user_input() { + format!("The `request_user_input` tool is available in {mode_name} mode.") + } else { + format!( + "The `request_user_input` tool is unavailable in {mode_name} mode. If you call it while in {mode_name} mode, it will return an error." + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn preset_names_use_mode_display_names() { + assert_eq!(plan_preset().name, ModeKind::Plan.display_name()); + assert_eq!(default_preset().name, ModeKind::Default.display_name()); + } + + #[test] + fn default_mode_instructions_replace_mode_names_placeholder() { + let default_instructions = default_preset() + .developer_instructions + .expect("default preset should include instructions") + .expect("default instructions should be set"); + + assert!(!default_instructions.contains(KNOWN_MODE_NAMES_PLACEHOLDER)); + assert!(!default_instructions.contains(REQUEST_USER_INPUT_AVAILABILITY_PLACEHOLDER)); + + let known_mode_names = format_mode_names(&TUI_VISIBLE_COLLABORATION_MODES); + let expected_snippet = format!("Known mode names are {known_mode_names}."); + assert!(default_instructions.contains(&expected_snippet)); + + let expected_availability_message = + request_user_input_availability_message(ModeKind::Default); + assert!(default_instructions.contains(&expected_availability_message)); } } diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index 6e1da5641f..134294d428 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -9,27 +9,15 @@ use crate::tools::handlers::parse_arguments; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; use codex_protocol::config_types::ModeKind; +use codex_protocol::config_types::TUI_VISIBLE_COLLABORATION_MODES; use codex_protocol::request_user_input::RequestUserInputArgs; -const REQUEST_USER_INPUT_ALLOWED_MODES: [ModeKind; 1] = [ModeKind::Plan]; - -fn request_user_input_mode_name(mode: ModeKind) -> &'static str { - match mode { - ModeKind::Plan => "Plan", - ModeKind::Default => "Default", - ModeKind::Execute => "Execute", - ModeKind::PairProgramming => "Pair Programming", - } -} - fn format_allowed_modes() -> String { - let mut mode_names = Vec::with_capacity(REQUEST_USER_INPUT_ALLOWED_MODES.len()); - for mode in REQUEST_USER_INPUT_ALLOWED_MODES { - let name = request_user_input_mode_name(mode); - if !mode_names.contains(&name) { - mode_names.push(name); - } - } + let mode_names: Vec<&str> = TUI_VISIBLE_COLLABORATION_MODES + .into_iter() + .filter(|mode| mode.allows_request_user_input()) + .map(ModeKind::display_name) + .collect(); match mode_names.as_slice() { [] => "no modes".to_string(), @@ -39,15 +27,11 @@ fn format_allowed_modes() -> String { } } -fn request_user_input_is_available_in_mode(mode: ModeKind) -> bool { - REQUEST_USER_INPUT_ALLOWED_MODES.contains(&mode) -} - pub(crate) fn request_user_input_unavailable_message(mode: ModeKind) -> Option { - if request_user_input_is_available_in_mode(mode) { + if mode.allows_request_user_input() { None } else { - let mode_name = request_user_input_mode_name(mode); + let mode_name = mode.display_name(); Some(format!( "request_user_input is unavailable in {mode_name} mode" )) @@ -134,22 +118,10 @@ mod tests { #[test] fn request_user_input_mode_availability_is_plan_only() { - assert_eq!( - request_user_input_is_available_in_mode(ModeKind::Plan), - true - ); - assert_eq!( - request_user_input_is_available_in_mode(ModeKind::Default), - false - ); - assert_eq!( - request_user_input_is_available_in_mode(ModeKind::Execute), - false - ); - assert_eq!( - request_user_input_is_available_in_mode(ModeKind::PairProgramming), - false - ); + assert!(ModeKind::Plan.allows_request_user_input()); + assert!(!ModeKind::Default.allows_request_user_input()); + assert!(!ModeKind::Execute.allows_request_user_input()); + assert!(!ModeKind::PairProgramming.allows_request_user_input()); } #[test] diff --git a/codex-rs/core/templates/collaboration_mode/default.md b/codex-rs/core/templates/collaboration_mode/default.md index c8154d10d9..4efd963ba0 100644 --- a/codex-rs/core/templates/collaboration_mode/default.md +++ b/codex-rs/core/templates/collaboration_mode/default.md @@ -2,8 +2,10 @@ You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. +Your active mode changes only when new developer instructions with a different `...` change it; user requests or tool descriptions do not change mode by themselves. Known mode names are {{KNOWN_MODE_NAMES}}. + ## request_user_input availability -The `request_user_input` tool is unavailable in Default mode. If you call it while in Default mode, it will return an error. +{{REQUEST_USER_INPUT_AVAILABILITY}} If a decision is necessary and cannot be discovered from local context, ask the user directly. However, in Default mode you should strongly prefer executing the user's request rather than stopping to ask questions. diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index f7d7b4f431..7586212adb 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -193,6 +193,27 @@ pub enum ModeKind { Execute, } +pub const TUI_VISIBLE_COLLABORATION_MODES: [ModeKind; 2] = [ModeKind::Default, ModeKind::Plan]; + +impl ModeKind { + pub const fn display_name(self) -> &'static str { + match self { + Self::Plan => "Plan", + Self::Default => "Default", + Self::PairProgramming => "Pair Programming", + Self::Execute => "Execute", + } + } + + pub const fn is_tui_visible(self) -> bool { + matches!(self, Self::Plan | Self::Default) + } + + pub const fn allows_request_user_input(self) -> bool { + matches!(self, Self::Plan) + } +} + /// Collaboration mode for a Codex session. #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)] #[serde(rename_all = "lowercase")] @@ -324,4 +345,17 @@ mod tests { assert_eq!(ModeKind::Default, mode); } } + + #[test] + fn tui_visible_collaboration_modes_match_mode_kind_visibility() { + let expected = [ModeKind::Default, ModeKind::Plan]; + assert_eq!(expected, TUI_VISIBLE_COLLABORATION_MODES); + + for mode in TUI_VISIBLE_COLLABORATION_MODES { + assert!(mode.is_tui_visible()); + } + + assert!(!ModeKind::PairProgramming.is_tui_visible()); + assert!(!ModeKind::Execute.is_tui_visible()); + } } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 4627613764..ee8400bbf0 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5490,11 +5490,10 @@ impl ChatWidget { if !self.collaboration_modes_enabled() { return None; } - match self.active_mode_kind() { - ModeKind::Plan => Some("Plan"), - ModeKind::Default => Some("Default"), - ModeKind::PairProgramming | ModeKind::Execute => None, - } + let active_mode = self.active_mode_kind(); + active_mode + .is_tui_visible() + .then_some(active_mode.display_name()) } fn collaboration_mode_indicator(&self) -> Option { diff --git a/codex-rs/tui/src/collaboration_modes.rs b/codex-rs/tui/src/collaboration_modes.rs index 3595cf5691..1e5676ccde 100644 --- a/codex-rs/tui/src/collaboration_modes.rs +++ b/codex-rs/tui/src/collaboration_modes.rs @@ -2,15 +2,11 @@ use codex_core::models_manager::manager::ModelsManager; use codex_protocol::config_types::CollaborationModeMask; use codex_protocol::config_types::ModeKind; -fn is_tui_mode(kind: ModeKind) -> bool { - matches!(kind, ModeKind::Plan | ModeKind::Default) -} - fn filtered_presets(models_manager: &ModelsManager) -> Vec { models_manager .list_collaboration_modes() .into_iter() - .filter(|mask| mask.mode.is_some_and(is_tui_mode)) + .filter(|mask| mask.mode.is_some_and(ModeKind::is_tui_visible)) .collect() } @@ -31,7 +27,7 @@ pub(crate) fn mask_for_kind( models_manager: &ModelsManager, kind: ModeKind, ) -> Option { - if !is_tui_mode(kind) { + if !kind.is_tui_visible() { return None; } filtered_presets(models_manager) From cd5f49a6192f2d306be1ff41e6992e95c826620e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Wed, 4 Feb 2026 23:12:59 -0800 Subject: [PATCH 48/90] Make steer stable by default (#10690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes the Steer feature from Experimental to Stable and enables it by default. ## What is Steer mode? Steer mode changes how message submission works in the TUI: - **With Steer enabled (new default)**: - `Enter` submits messages immediately, even when a task is running - `Tab` queues messages when a task is running (allows building up a queue) - **With Steer disabled (old behavior)**: - `Enter` queues messages when a task is running - This preserves the previous "queue while a task is running" behavior ## How Steer vs Queue work The key difference is in the submission behavior: 1. **Steer mode** (`steer_enabled = true`): - Enter → `InputResult::Submitted` → sends immediately via `submit_user_message()` - Tab → `InputResult::Queued` → queues via `queue_user_message()` if a task is running - This gives users direct control: Enter for immediate submission, Tab for queuing 2. **Queue mode** (`steer_enabled = false`, previous default): - Enter → `InputResult::Queued` → always queues when a task is running - Tab → `InputResult::Queued` → queues when a task is running - This preserves the original behavior where Enter respects the running task queue ## Implementation details The behavior is controlled in `ChatComposer::handle_key_event_without_popup()`: - When `steer_enabled` is true, Enter calls `handle_submission(false)` (submit immediately) - When `steer_enabled` is false, Enter calls `handle_submission(true)` (queue) See `codex-rs/tui/src/bottom_pane/chat_composer.rs` for the implementation. ## Documentation For more details on the chat composer behavior, see: - [TUI Chat Composer documentation](docs/tui-chat-composer.md) - Feature flag definition: `codex-rs/core/src/features.rs` --- codex-rs/core/src/features.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index f6c286c571..e14373f858 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -556,12 +556,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::Steer, key: "steer", - stage: Stage::Experimental { - name: "Steer conversation", - menu_description: "Enter submits immediately; Tab queues messages when a task is running.", - announcement: "NEW! Try Steer mode: Enter submits immediately, Tab queues. Enable in /experimental!", - }, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::CollaborationModes, From dc7007beaae33f35439f8881a792387fa677199c Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Wed, 4 Feb 2026 23:24:06 -0800 Subject: [PATCH 49/90] Fix remote compaction estimator/payload instruction small mismatch (#10692) ## Summary This PR fixes a deterministic mismatch in remote compaction where pre-trim estimation and the `/v1/responses/compact` payload could use different base instructions. Before this change: - pre-trim estimation used model-derived instructions (`model_info.get_model_instructions(...)`) - compact payload used session base instructions (`sess.get_base_instructions()`) After this change: - remote pre-trim estimation and compact payload both use the same `BaseInstructions` instance from session state. ## Changes - Added a shared estimator entry point in `ContextManager`: - `estimate_token_count_with_base_instructions(&self, base_instructions: &BaseInstructions) -> Option` - Kept `estimate_token_count(&TurnContext)` as a thin wrapper that resolves model/personality instructions and delegates to the new helper. - Updated remote compaction flow to fetch base instructions once and reuse it for both: - trim preflight estimation - compact request payload construction - Added regression coverage for parity and behavior: - unit test verifying explicit-base estimator behavior - integration test proving remote compaction uses session override instructions and trims accordingly ## Why this matters This removes a deterministic divergence source where pre-trim could think the request fits while the actual compact request exceeded context because its instructions were longer/different. ## Scope In scope: - estimator/payload base-instructions parity in remote compaction Out of scope: - retry-on-`context_length_exceeded` - compaction threshold/headroom policy changes - broader trimming policy changes ## Codex author: `codex fork 019c2b24-c2df-7b31-a482-fb8cf7a28559` --- codex-rs/core/src/codex.rs | 49 +++- codex-rs/core/src/compact_remote.rs | 14 +- codex-rs/core/src/context_manager/history.rs | 15 +- .../core/src/context_manager/history_tests.rs | 27 +++ codex-rs/core/tests/suite/compact_remote.rs | 219 ++++++++++++++++++ 5 files changed, 314 insertions(+), 10 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index dda701d69e..45b0a34b76 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2117,10 +2117,10 @@ impl Session { } pub(crate) async fn recompute_token_usage(&self, turn_context: &TurnContext) { - let Some(estimated_total_tokens) = self - .clone_history() - .await - .estimate_token_count(turn_context) + let history = self.clone_history().await; + let base_instructions = self.get_base_instructions().await; + let Some(estimated_total_tokens) = + history.estimate_token_count_with_base_instructions(&base_instructions) else { return; }; @@ -4782,6 +4782,7 @@ mod tests { use crate::turn_diff_tracker::TurnDiffTracker; use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AuthMode; + use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseItem; use std::path::Path; @@ -5061,6 +5062,46 @@ mod tests { assert_eq!(actual, Some(info2)); } + #[tokio::test] + async fn recompute_token_usage_uses_session_base_instructions() { + let (session, turn_context) = make_session_and_context().await; + + let override_instructions = "SESSION_OVERRIDE_INSTRUCTIONS_ONLY".repeat(120); + { + let mut state = session.state.lock().await; + state.session_configuration.base_instructions = override_instructions.clone(); + } + + let item = user_message("hello"); + session + .record_into_history(std::slice::from_ref(&item), &turn_context) + .await; + + let history = session.clone_history().await; + let session_base_instructions = BaseInstructions { + text: override_instructions, + }; + let expected_tokens = history + .estimate_token_count_with_base_instructions(&session_base_instructions) + .expect("estimate with session base instructions"); + let model_estimated_tokens = history + .estimate_token_count(&turn_context) + .expect("estimate with model instructions"); + assert_ne!(expected_tokens, model_estimated_tokens); + + session.recompute_token_usage(&turn_context).await; + + let actual_tokens = session + .state + .lock() + .await + .token_info() + .expect("token info") + .last_token_usage + .total_tokens; + assert_eq!(actual_tokens, expected_tokens.max(0)); + } + #[tokio::test] async fn record_initial_history_reconstructs_forked_transcript() { let (session, turn_context) = make_session_and_context().await; diff --git a/codex-rs/core/src/compact_remote.rs b/codex-rs/core/src/compact_remote.rs index f46084bd9c..f627bcb473 100644 --- a/codex-rs/core/src/compact_remote.rs +++ b/codex-rs/core/src/compact_remote.rs @@ -12,6 +12,7 @@ use crate::protocol::RolloutItem; use crate::protocol::TurnStartedEvent; use codex_protocol::items::ContextCompactionItem; use codex_protocol::items::TurnItem; +use codex_protocol::models::BaseInstructions; use codex_protocol::models::ResponseItem; use tracing::info; @@ -49,8 +50,12 @@ async fn run_remote_compact_task_inner_impl( sess.emit_turn_item_started(turn_context, &compaction_item) .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()); + let base_instructions = sess.get_base_instructions().await; + let deleted_items = trim_function_call_history_to_fit_context_window( + &mut history, + turn_context.as_ref(), + &base_instructions, + ); if deleted_items > 0 { info!( turn_id = %turn_context.sub_id, @@ -71,7 +76,7 @@ async fn run_remote_compact_task_inner_impl( input: history.for_prompt(), tools: vec![], parallel_tool_calls: false, - base_instructions: sess.get_base_instructions().await, + base_instructions, personality: turn_context.personality, output_schema: None, }; @@ -107,6 +112,7 @@ async fn run_remote_compact_task_inner_impl( fn trim_function_call_history_to_fit_context_window( history: &mut ContextManager, turn_context: &TurnContext, + base_instructions: &BaseInstructions, ) -> usize { let mut deleted_items = 0usize; let Some(context_window) = turn_context.model_context_window() else { @@ -114,7 +120,7 @@ fn trim_function_call_history_to_fit_context_window( }; while history - .estimate_token_count(turn_context) + .estimate_token_count_with_base_instructions(base_instructions) .is_some_and(|estimated_tokens| estimated_tokens > context_window) { let Some(last_item) = history.raw_items().last() else { diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index b1bf4086da..a67ea90d3d 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -9,6 +9,7 @@ use crate::truncate::approx_tokens_from_byte_count; use crate::truncate::truncate_function_output_items_with_policy; use crate::truncate::truncate_text; use crate::user_shell_command::is_user_shell_command_text; +use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; @@ -88,8 +89,18 @@ impl ContextManager { pub(crate) fn estimate_token_count(&self, turn_context: &TurnContext) -> Option { let model_info = &turn_context.model_info; let personality = turn_context.personality.or(turn_context.config.personality); - let base_instructions = model_info.get_model_instructions(personality); - let base_tokens = i64::try_from(approx_token_count(&base_instructions)).unwrap_or(i64::MAX); + let base_instructions = BaseInstructions { + text: model_info.get_model_instructions(personality), + }; + self.estimate_token_count_with_base_instructions(&base_instructions) + } + + pub(crate) fn estimate_token_count_with_base_instructions( + &self, + base_instructions: &BaseInstructions, + ) -> Option { + let base_tokens = + i64::try_from(approx_token_count(&base_instructions.text)).unwrap_or(i64::MAX); let items_tokens = self.items.iter().fold(0i64, |acc, item| { acc.saturating_add(estimate_item_token_count(item)) diff --git a/codex-rs/core/src/context_manager/history_tests.rs b/codex-rs/core/src/context_manager/history_tests.rs index c1801e202d..87defcd1d6 100644 --- a/codex-rs/core/src/context_manager/history_tests.rs +++ b/codex-rs/core/src/context_manager/history_tests.rs @@ -2,6 +2,7 @@ use super::*; use crate::truncate; use crate::truncate::TruncationPolicy; use codex_git::GhostCommit; +use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; use codex_protocol::models::FunctionCallOutputBody; use codex_protocol::models::FunctionCallOutputContentItem; @@ -103,6 +104,10 @@ fn truncate_exec_output(content: &str) -> String { truncate::truncate_text(content, TruncationPolicy::Tokens(EXEC_FORMAT_MAX_TOKENS)) } +fn approx_token_count_for_text(text: &str) -> i64 { + i64::try_from(text.len().saturating_add(3) / 4).unwrap_or(i64::MAX) +} + #[test] fn filters_non_api_messages() { let mut h = ContextManager::default(); @@ -250,6 +255,28 @@ fn get_history_for_prompt_drops_ghost_commits() { assert_eq!(filtered, vec![]); } +#[test] +fn estimate_token_count_with_base_instructions_uses_provided_text() { + let history = create_history_with_items(vec![assistant_msg("hello from history")]); + let short_base = BaseInstructions { + text: "short".to_string(), + }; + let long_base = BaseInstructions { + text: "x".repeat(1_000), + }; + + let short_estimate = history + .estimate_token_count_with_base_instructions(&short_base) + .expect("token estimate"); + let long_estimate = history + .estimate_token_count_with_base_instructions(&long_base) + .expect("token estimate"); + + let expected_delta = approx_token_count_for_text(&long_base.text) + - approx_token_count_for_text(&short_base.text); + assert_eq!(long_estimate - short_estimate, expected_delta); +} + #[test] fn remove_first_item_removes_matching_output_for_function_call() { let items = vec![ diff --git a/codex-rs/core/tests/suite/compact_remote.rs b/codex-rs/core/tests/suite/compact_remote.rs index b0aff9efaa..435aa8900c 100644 --- a/codex-rs/core/tests/suite/compact_remote.rs +++ b/codex-rs/core/tests/suite/compact_remote.rs @@ -25,6 +25,21 @@ use core_test_support::wait_for_event; use core_test_support::wait_for_event_match; use pretty_assertions::assert_eq; +fn approx_token_count(text: &str) -> i64 { + i64::try_from(text.len().saturating_add(3) / 4).unwrap_or(i64::MAX) +} + +fn estimate_compact_input_tokens(request: &responses::ResponsesRequest) -> i64 { + request.input().into_iter().fold(0i64, |acc, item| { + acc.saturating_add(approx_token_count(&item.to_string())) + }) +} + +fn estimate_compact_payload_tokens(request: &responses::ResponsesRequest) -> i64 { + estimate_compact_input_tokens(request) + .saturating_add(approx_token_count(&request.instructions_text())) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn remote_compact_replaces_history_for_followups() -> Result<()> { skip_if_no_network!(Ok(())); @@ -351,6 +366,210 @@ async fn remote_compact_trims_function_call_history_to_fit_context_window() -> R Ok(()) } +#[cfg_attr(target_os = "windows", ignore)] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result<()> { + skip_if_no_network!(Ok(())); + + let first_user_message = "turn with baseline shell call"; + let second_user_message = "turn with trailing shell call"; + let baseline_retained_call_id = "baseline-retained-call"; + let baseline_trailing_call_id = "baseline-trailing-call"; + let override_retained_call_id = "override-retained-call"; + let override_trailing_call_id = "override-trailing-call"; + let retained_command = "printf retained-shell-output"; + let trailing_command = "printf trailing-shell-output"; + + let baseline_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(200_000); + }), + ) + .await?; + let baseline_codex = baseline_harness.test().codex.clone(); + + responses::mount_sse_sequence( + baseline_harness.server(), + vec![ + sse(vec![ + responses::ev_shell_command_call(baseline_retained_call_id, retained_command), + responses::ev_completed("baseline-retained-call-response"), + ]), + sse(vec![ + responses::ev_assistant_message("baseline-retained-assistant", "retained complete"), + responses::ev_completed("baseline-retained-final-response"), + ]), + sse(vec![ + responses::ev_shell_command_call(baseline_trailing_call_id, trailing_command), + responses::ev_completed("baseline-trailing-call-response"), + ]), + sse(vec![responses::ev_completed( + "baseline-trailing-final-response", + )]), + ], + ) + .await; + + baseline_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(&baseline_codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + baseline_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(&baseline_codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let baseline_compact_mock = responses::mount_compact_json_once( + baseline_harness.server(), + serde_json::json!({ "output": [] }), + ) + .await; + + baseline_codex.submit(Op::Compact).await?; + wait_for_event(&baseline_codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let baseline_compact_request = baseline_compact_mock.single_request(); + assert!( + baseline_compact_request.has_function_call(baseline_retained_call_id), + "expected baseline compact request to retain older function call history" + ); + assert!( + baseline_compact_request.has_function_call(baseline_trailing_call_id), + "expected baseline compact request to retain trailing function call history" + ); + + let baseline_input_tokens = estimate_compact_input_tokens(&baseline_compact_request); + let baseline_payload_tokens = estimate_compact_payload_tokens(&baseline_compact_request); + + let override_base_instructions = + format!("REMOTE_BASE_INSTRUCTIONS_OVERRIDE {}", "x".repeat(120_000)); + let override_context_window = baseline_payload_tokens.saturating_add(1_000); + let pretrim_override_estimate = + baseline_input_tokens.saturating_add(approx_token_count(&override_base_instructions)); + assert!( + pretrim_override_estimate > override_context_window, + "expected override instructions to push pre-trim estimate past the context window" + ); + + let override_harness = TestCodexHarness::with_builder( + test_codex() + .with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing()) + .with_config({ + let override_base_instructions = override_base_instructions.clone(); + move |config| { + config.features.enable(Feature::RemoteCompaction); + config.model_context_window = Some(override_context_window); + config.base_instructions = Some(override_base_instructions); + } + }), + ) + .await?; + let override_codex = override_harness.test().codex.clone(); + + responses::mount_sse_sequence( + override_harness.server(), + vec![ + sse(vec![ + responses::ev_shell_command_call(override_retained_call_id, retained_command), + responses::ev_completed("override-retained-call-response"), + ]), + sse(vec![ + responses::ev_assistant_message("override-retained-assistant", "retained complete"), + responses::ev_completed("override-retained-final-response"), + ]), + sse(vec![ + responses::ev_shell_command_call(override_trailing_call_id, trailing_command), + responses::ev_completed("override-trailing-call-response"), + ]), + sse(vec![responses::ev_completed( + "override-trailing-final-response", + )]), + ], + ) + .await; + + override_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(&override_codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + override_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(&override_codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let override_compact_mock = responses::mount_compact_json_once( + override_harness.server(), + serde_json::json!({ "output": [] }), + ) + .await; + + override_codex.submit(Op::Compact).await?; + wait_for_event(&override_codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let override_compact_request = override_compact_mock.single_request(); + assert_eq!( + override_compact_request.instructions_text(), + override_base_instructions + ); + assert!( + override_compact_request.has_function_call(override_retained_call_id), + "expected remote compact request to preserve older function call history" + ); + assert!( + !override_compact_request.has_function_call(override_trailing_call_id), + "expected remote compact request to trim trailing function call history with override instructions" + ); + + 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 1e1146cd293866ab31ea24e29bc53d0305cac43f Mon Sep 17 00:00:00 2001 From: xl-openai Date: Thu, 5 Feb 2026 00:27:16 -0800 Subject: [PATCH 50/90] Reload cloud requirements after user login (#10725) Reload cloud requirements after user login so it could take effect immediately. --- .../app-server/src/codex_message_processor.rs | 106 ++++++++++++++++-- codex-rs/tui/src/lib.rs | 22 +++- 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index ef7e175b10..760b27a53b 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -140,6 +140,7 @@ use codex_app_server_protocol::UserSavedConfig; use codex_app_server_protocol::build_turns_from_event_msgs; use codex_backend_client::Client as BackendClient; use codex_chatgpt::connectors; +use codex_cloud_requirements::cloud_requirements_loader; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::CodexThread; @@ -162,6 +163,7 @@ use codex_core::config::edit::ConfigEditsBuilder; use codex_core::config::types::McpServerTransportConfig; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::default_client::get_codex_user_agent; +use codex_core::default_client::set_default_client_residency_requirement; use codex_core::error::CodexErr; use codex_core::exec::ExecParams; use codex_core::exec_env::create_env; @@ -218,6 +220,7 @@ use std::io::Error as IoError; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use std::sync::RwLock; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::time::Duration; @@ -277,7 +280,7 @@ pub(crate) struct CodexMessageProcessor { codex_linux_sandbox_exe: Option, config: Arc, cli_overrides: Vec<(String, TomlValue)>, - cloud_requirements: CloudRequirementsLoader, + cloud_requirements: Arc>, conversation_listeners: HashMap>, listener_thread_ids_by_subscription: HashMap, active_login: Arc>>, @@ -349,7 +352,7 @@ impl CodexMessageProcessor { codex_linux_sandbox_exe, config, cli_overrides, - cloud_requirements, + cloud_requirements: Arc::new(RwLock::new(cloud_requirements)), conversation_listeners: HashMap::new(), listener_thread_ids_by_subscription: HashMap::new(), active_login: Arc::new(Mutex::new(None)), @@ -362,9 +365,10 @@ impl CodexMessageProcessor { } async fn load_latest_config(&self) -> Result { + let cloud_requirements = self.current_cloud_requirements(); codex_core::config::ConfigBuilder::default() .cli_overrides(self.cli_overrides.clone()) - .cloud_requirements(self.cloud_requirements.clone()) + .cloud_requirements(cloud_requirements) .build() .await .map_err(|err| JSONRPCErrorError { @@ -374,6 +378,13 @@ impl CodexMessageProcessor { }) } + fn current_cloud_requirements(&self) -> CloudRequirementsLoader { + self.cloud_requirements + .read() + .map(|guard| guard.clone()) + .unwrap_or_default() + } + fn review_request_from_target( target: ApiReviewTarget, ) -> Result<(ReviewRequest, String), JSONRPCErrorError> { @@ -834,6 +845,9 @@ impl CodexMessageProcessor { let outgoing_clone = self.outgoing.clone(); let active_login = self.active_login.clone(); let auth_manager = self.auth_manager.clone(); + let cloud_requirements = self.cloud_requirements.clone(); + let chatgpt_base_url = self.config.chatgpt_base_url.clone(); + let cli_overrides = self.cli_overrides.clone(); let auth_url = server.auth_url.clone(); tokio::spawn(async move { let (success, error_msg) = match tokio::time::timeout( @@ -863,6 +877,16 @@ impl CodexMessageProcessor { if success { auth_manager.reload(); + replace_cloud_requirements_loader( + cloud_requirements.as_ref(), + auth_manager.clone(), + chatgpt_base_url, + ); + sync_default_client_residency_requirement( + &cli_overrides, + cloud_requirements.as_ref(), + ) + .await; // Notify clients with the actual current auth mode. let current_auth_method = auth_manager @@ -927,6 +951,9 @@ impl CodexMessageProcessor { let outgoing_clone = self.outgoing.clone(); let active_login = self.active_login.clone(); let auth_manager = self.auth_manager.clone(); + let cloud_requirements = self.cloud_requirements.clone(); + let chatgpt_base_url = self.config.chatgpt_base_url.clone(); + let cli_overrides = self.cli_overrides.clone(); let auth_url = server.auth_url.clone(); tokio::spawn(async move { let (success, error_msg) = match tokio::time::timeout( @@ -956,6 +983,16 @@ impl CodexMessageProcessor { if success { auth_manager.reload(); + replace_cloud_requirements_loader( + cloud_requirements.as_ref(), + auth_manager.clone(), + chatgpt_base_url, + ); + sync_default_client_residency_requirement( + &cli_overrides, + cloud_requirements.as_ref(), + ) + .await; // Notify clients with the actual current auth mode. let current_auth_method = auth_manager @@ -1123,6 +1160,16 @@ impl CodexMessageProcessor { return; } self.auth_manager.reload(); + replace_cloud_requirements_loader( + self.cloud_requirements.as_ref(), + self.auth_manager.clone(), + self.config.chatgpt_base_url.clone(), + ); + sync_default_client_residency_requirement( + &self.cli_overrides, + self.cloud_requirements.as_ref(), + ) + .await; self.outgoing .send_response(request_id, LoginAccountResponse::ChatgptAuthTokens {}) @@ -1561,11 +1608,12 @@ impl CodexMessageProcessor { ); } + let cloud_requirements = self.current_cloud_requirements(); let config = match derive_config_from_params( &self.cli_overrides, Some(request_overrides), typesafe_overrides, - &self.cloud_requirements, + &cloud_requirements, ) .await { @@ -1647,11 +1695,12 @@ impl CodexMessageProcessor { ); typesafe_overrides.ephemeral = ephemeral; + let cloud_requirements = self.current_cloud_requirements(); let config = match derive_config_from_params( &self.cli_overrides, config, typesafe_overrides, - &self.cloud_requirements, + &cloud_requirements, ) .await { @@ -2440,12 +2489,13 @@ impl CodexMessageProcessor { ); // Derive a Config using the same logic as new conversation, honoring overrides if provided. + let cloud_requirements = self.current_cloud_requirements(); let config = match derive_config_for_cwd( &self.cli_overrides, request_overrides, typesafe_overrides, history_cwd, - &self.cloud_requirements, + &cloud_requirements, ) .await { @@ -2633,12 +2683,13 @@ impl CodexMessageProcessor { None, ); // Derive a Config using the same logic as new conversation, honoring overrides if provided. + let cloud_requirements = self.current_cloud_requirements(); let config = match derive_config_for_cwd( &self.cli_overrides, request_overrides, typesafe_overrides, history_cwd, - &self.cloud_requirements, + &cloud_requirements, ) .await { @@ -3438,12 +3489,13 @@ impl CodexMessageProcessor { ), }; + let cloud_requirements = self.current_cloud_requirements(); let config = match derive_config_for_cwd( &self.cli_overrides, request_overrides, typesafe_overrides, history_cwd, - &self.cloud_requirements, + &cloud_requirements, ) .await { @@ -3627,12 +3679,13 @@ impl CodexMessageProcessor { ), }; + let cloud_requirements = self.current_cloud_requirements(); let config = match derive_config_for_cwd( &self.cli_overrides, request_overrides, typesafe_overrides, history_cwd, - &self.cloud_requirements, + &cloud_requirements, ) .await { @@ -4944,6 +4997,41 @@ fn validate_dynamic_tools( Ok(()) } +fn replace_cloud_requirements_loader( + cloud_requirements: &RwLock, + auth_manager: Arc, + chatgpt_base_url: String, +) { + let loader = cloud_requirements_loader(auth_manager, chatgpt_base_url); + if let Ok(mut guard) = cloud_requirements.write() { + *guard = loader; + } else { + warn!("failed to update cloud requirements loader"); + } +} + +async fn sync_default_client_residency_requirement( + cli_overrides: &[(String, TomlValue)], + cloud_requirements: &RwLock, +) { + let loader = cloud_requirements + .read() + .map(|guard| guard.clone()) + .unwrap_or_default(); + match codex_core::config::ConfigBuilder::default() + .cli_overrides(cli_overrides.to_vec()) + .cloud_requirements(loader) + .build() + .await + { + Ok(config) => set_default_client_residency_requirement(config.enforce_residency.value()), + Err(err) => warn!( + error = %err, + "failed to sync default client residency requirement after auth refresh" + ), + } +} + /// Derive the effective [`Config`] by layering three override sources. /// /// Precedence (lowest to highest): diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 2dbc1fcaf9..95e26fe675 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -413,7 +413,7 @@ async fn run_ratatui_app( initial_config: Config, overrides: ConfigOverrides, cli_kv_overrides: Vec<(String, toml::Value)>, - cloud_requirements: CloudRequirementsLoader, + mut cloud_requirements: CloudRequirementsLoader, feedback: codex_feedback::CodexFeedback, ) -> color_eyre::Result { color_eyre::install()?; @@ -470,9 +470,10 @@ async fn run_ratatui_app( should_show_onboarding(login_status, &initial_config, should_show_trust_screen_flag); let config = if should_show_onboarding { + let show_login_screen = should_show_login_screen(login_status, &initial_config); let onboarding_result = run_onboarding_app( OnboardingScreenArgs { - show_login_screen: should_show_login_screen(login_status, &initial_config), + show_login_screen, show_trust_screen: should_show_trust_screen_flag, login_status, auth_manager: auth_manager.clone(), @@ -493,9 +494,19 @@ async fn run_ratatui_app( exit_reason: ExitReason::UserRequested, }); } - // 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() { + // If this onboarding run included the login step, always refresh cloud requirements and + // rebuild config. This avoids missing newly available cloud requirements due to login + // status detection edge cases. + if show_login_screen { + cloud_requirements = cloud_requirements_loader( + auth_manager.clone(), + initial_config.chatgpt_base_url.clone(), + ); + } + + // If the user made an explicit trust decision, or we showed the login flow, reload config + // so current process state reflects persisted trust/auth changes. + if onboarding_result.directory_trust_decision.is_some() || show_login_screen { load_config_or_exit( cli_kv_overrides.clone(), overrides.clone(), @@ -668,6 +679,7 @@ async fn run_ratatui_app( } _ => config, }; + set_default_client_residency_requirement(config.enforce_residency.value()); let active_profile = config.active_profile.clone(); let should_show_trust_screen = should_show_trust_screen(&config); From fe8b474acd43cb8894d661944c6b5c8db0ef0ad1 Mon Sep 17 00:00:00 2001 From: Dylan Hurd Date: Thu, 5 Feb 2026 00:40:05 -0800 Subject: [PATCH 51/90] fix(core,app-server) resume with different model (#10719) ## Summary When resuming with a different model, we should also append a developer message with the model instructions ## Testing - [x] Added unit tests --- .../app-server/tests/suite/send_message.rs | 142 +++++++++++++++ codex-rs/core/src/codex.rs | 81 +++++---- codex-rs/core/src/state/session.rs | 3 + codex-rs/core/tests/suite/resume.rs | 163 ++++++++++++++++-- 4 files changed, 347 insertions(+), 42 deletions(-) diff --git a/codex-rs/app-server/tests/suite/send_message.rs b/codex-rs/app-server/tests/suite/send_message.rs index 814352a007..ecb742aff0 100644 --- a/codex-rs/app-server/tests/suite/send_message.rs +++ b/codex-rs/app-server/tests/suite/send_message.rs @@ -1,5 +1,7 @@ use anyhow::Result; use app_test_support::McpProcess; +use app_test_support::create_fake_rollout; +use app_test_support::rollout_path; use app_test_support::to_response; use codex_app_server_protocol::AddConversationListenerParams; use codex_app_server_protocol::AddConversationSubscriptionResponse; @@ -9,18 +11,25 @@ use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::NewConversationParams; use codex_app_server_protocol::NewConversationResponse; use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ResumeConversationParams; +use codex_app_server_protocol::ResumeConversationResponse; use codex_app_server_protocol::SendUserMessageParams; use codex_app_server_protocol::SendUserMessageResponse; use codex_execpolicy::Policy; use codex_protocol::ThreadId; +use codex_protocol::config_types::ReasoningSummary; use codex_protocol::models::ContentItem; use codex_protocol::models::DeveloperInstructions; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::RawResponseItemEvent; +use codex_protocol::protocol::RolloutItem; +use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::SandboxPolicy; +use codex_protocol::protocol::TurnContextItem; use core_test_support::responses; use pretty_assertions::assert_eq; +use std::io::Write; use std::path::Path; use std::path::PathBuf; use tempfile::TempDir; @@ -263,6 +272,114 @@ async fn test_send_message_session_not_found() -> Result<()> { Ok(()) } +#[tokio::test] +async fn resume_with_model_mismatch_appends_model_switch_once() -> Result<()> { + let server = responses::start_mock_server().await; + let response_mock = responses::mount_sse_sequence( + &server, + vec![ + responses::sse(vec![ + responses::ev_response_created("resp-1"), + responses::ev_assistant_message("msg-1", "Done"), + responses::ev_completed("resp-1"), + ]), + responses::sse(vec![ + responses::ev_response_created("resp-2"), + responses::ev_assistant_message("msg-2", "Done again"), + responses::ev_completed("resp-2"), + ]), + ], + ) + .await; + + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri())?; + + let filename_ts = "2025-01-02T12-00-00"; + let meta_rfc3339 = "2025-01-02T12:00:00Z"; + let preview = "Resume me"; + let conversation_id = create_fake_rollout( + codex_home.path(), + filename_ts, + meta_rfc3339, + preview, + Some("mock_provider"), + None, + )?; + let rollout_path = rollout_path(codex_home.path(), filename_ts, &conversation_id); + append_rollout_turn_context(&rollout_path, meta_rfc3339, "previous-model")?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let resume_id = mcp + .send_resume_conversation_request(ResumeConversationParams { + path: Some(rollout_path.clone()), + conversation_id: None, + history: None, + overrides: Some(NewConversationParams { + model: Some("gpt-5.2-codex".to_string()), + ..Default::default() + }), + }) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("sessionConfigured"), + ) + .await??; + let resume_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(resume_id)), + ) + .await??; + let ResumeConversationResponse { + conversation_id, .. + } = to_response::(resume_resp)?; + + let add_listener_id = mcp + .send_add_conversation_listener_request(AddConversationListenerParams { + conversation_id, + experimental_raw_events: false, + }) + .await?; + let add_listener_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(add_listener_id)), + ) + .await??; + let AddConversationSubscriptionResponse { subscription_id: _ } = + to_response::<_>(add_listener_resp)?; + + send_message("hello after resume", conversation_id, &mut mcp).await?; + send_message("second turn", conversation_id, &mut mcp).await?; + + let requests = response_mock.requests(); + assert_eq!(requests.len(), 2, "expected two model requests"); + + let first_developer_texts = requests[0].message_input_texts("developer"); + let first_model_switch_count = first_developer_texts + .iter() + .filter(|text| text.contains("")) + .count(); + assert!( + first_model_switch_count >= 1, + "expected model switch message on first post-resume turn, got {first_developer_texts:?}" + ); + + let second_developer_texts = requests[1].message_input_texts("developer"); + let second_model_switch_count = second_developer_texts + .iter() + .filter(|text| text.contains("")) + .count(); + assert_eq!( + second_model_switch_count, 1, + "did not expect duplicate model switch message on second post-resume turn, got {second_developer_texts:?}" + ); + + Ok(()) +} + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -438,3 +555,28 @@ fn content_texts(content: &[ContentItem]) -> Vec<&str> { }) .collect() } + +fn append_rollout_turn_context(path: &Path, timestamp: &str, model: &str) -> std::io::Result<()> { + let line = RolloutLine { + timestamp: timestamp.to_string(), + item: RolloutItem::TurnContext(TurnContextItem { + cwd: PathBuf::from("/"), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::DangerFullAccess, + model: model.to_string(), + personality: None, + collaboration_mode: None, + effort: None, + summary: ReasoningSummary::Auto, + user_instructions: None, + developer_instructions: None, + final_output_json_schema: None, + truncation_policy: None, + }), + }; + let serialized = serde_json::to_string(&line).map_err(std::io::Error::other)?; + std::fs::OpenOptions::new() + .append(true) + .open(path)? + .write_all(format!("{serialized}\n").as_bytes()) +} diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 45b0a34b76..8567c9286a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1176,32 +1176,26 @@ impl Session { { let mut state = self.state.lock().await; state.initial_context_seeded = false; + state.pending_resume_previous_model = None; } // If resuming, warn when the last recorded model differs from the current one. - if let Some(prev) = rollout_items.iter().rev().find_map(|it| { - if let RolloutItem::TurnContext(ctx) = it { - Some(ctx.model.as_str()) - } else { - None - } - }) { - let curr = turn_context.model_info.slug.as_str(); - if prev != curr { - warn!( - "resuming session with different model: previous={prev}, current={curr}" - ); - self.send_event( - &turn_context, - EventMsg::Warning(WarningEvent { - message: format!( - "This session was recorded with model `{prev}` but is resuming with `{curr}`. \ + let curr = turn_context.model_info.slug.as_str(); + if let Some(prev) = Self::last_model_name(&rollout_items, curr) { + warn!("resuming session with different model: previous={prev}, current={curr}"); + self.send_event( + &turn_context, + EventMsg::Warning(WarningEvent { + message: format!( + "This session was recorded with model `{prev}` but is resuming with `{curr}`. \ Consider switching back to `{prev}` as it may affect Codex performance." - ), - }), - ) - .await; - } + ), + }), + ) + .await; + + let mut state = self.state.lock().await; + state.pending_resume_previous_model = Some(prev.to_string()); } // Always add response items to conversation history @@ -1260,6 +1254,21 @@ impl Session { } } + fn last_model_name<'a>(rollout_items: &'a [RolloutItem], current: &str) -> Option<&'a str> { + let previous = rollout_items.iter().rev().find_map(|it| { + if let RolloutItem::TurnContext(ctx) = it { + Some(ctx.model.as_str()) + } else { + None + } + })?; + if previous == current { + None + } else { + Some(previous) + } + } + fn last_token_info_from_rollout(rollout_items: &[RolloutItem]) -> Option { rollout_items.iter().rev().find_map(|item| match item { RolloutItem::EventMsg(EventMsg::TokenCount(ev)) => ev.info.clone(), @@ -1267,6 +1276,11 @@ impl Session { }) } + async fn take_pending_resume_previous_model(&self) -> Option { + let mut state = self.state.lock().await; + state.pending_resume_previous_model.take() + } + pub(crate) async fn update_settings( &self, updates: SessionSettingsUpdate, @@ -1504,10 +1518,12 @@ impl Session { fn build_model_instructions_update_item( &self, previous: Option<&Arc>, + resumed_model: Option<&str>, next: &TurnContext, ) -> Option { - let prev = previous?; - if prev.model_info.slug == next.model_info.slug { + let previous_model = + resumed_model.or_else(|| previous.map(|prev| prev.model_info.slug.as_str()))?; + if previous_model == next.model_info.slug { return None; } @@ -1522,6 +1538,7 @@ impl Session { fn build_settings_update_items( &self, previous_context: Option<&Arc>, + resumed_model: Option<&str>, current_context: &TurnContext, ) -> Vec { let mut update_items = Vec::new(); @@ -1540,9 +1557,11 @@ impl Session { { update_items.push(collaboration_mode_item); } - if let Some(model_instructions_item) = - self.build_model_instructions_update_item(previous_context, current_context) - { + if let Some(model_instructions_item) = self.build_model_instructions_update_item( + previous_context, + resumed_model, + current_context, + ) { update_items.push(model_instructions_item); } if let Some(personality_item) = @@ -2819,8 +2838,12 @@ mod handlers { // Attempt to inject input into current task if let Err(items) = sess.inject_input(items).await { sess.seed_initial_context_if_needed(¤t_context).await; - let update_items = - sess.build_settings_update_items(previous_context.as_ref(), ¤t_context); + let resumed_model = sess.take_pending_resume_previous_model().await; + let update_items = sess.build_settings_update_items( + previous_context.as_ref(), + resumed_model.as_deref(), + ¤t_context, + ); if !update_items.is_empty() { sess.record_conversation_items(¤t_context, &update_items) .await; diff --git a/codex-rs/core/src/state/session.rs b/codex-rs/core/src/state/session.rs index deee0d0c7c..29bc14d1cc 100644 --- a/codex-rs/core/src/state/session.rs +++ b/codex-rs/core/src/state/session.rs @@ -24,6 +24,8 @@ pub(crate) struct SessionState { /// TODO(owen): This is a temporary solution to avoid updating a thread's updated_at /// timestamp when resuming a session. Remove this once SQLite is in place. pub(crate) initial_context_seeded: bool, + /// Previous rollout model for one-shot model-switch handling on first turn after resume. + pub(crate) pending_resume_previous_model: Option, } impl SessionState { @@ -38,6 +40,7 @@ impl SessionState { dependency_env: HashMap::new(), mcp_dependency_prompted: HashSet::new(), initial_context_seeded: false, + pending_resume_previous_model: None, } } diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs index a7be42f430..c0bdcd4fe2 100644 --- a/codex-rs/core/tests/suite/resume.rs +++ b/codex-rs/core/tests/suite/resume.rs @@ -9,6 +9,7 @@ use core_test_support::responses::ev_completed; use core_test_support::responses::ev_reasoning_item; use core_test_support::responses::ev_response_created; use core_test_support::responses::mount_sse_once; +use core_test_support::responses::mount_sse_sequence; use core_test_support::responses::sse; use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; @@ -182,12 +183,22 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { .unwrap_or_default() .to_string(); - let resumed_sse = sse(vec![ - ev_response_created("resp-resume"), - ev_assistant_message("msg-2", "Resumed turn"), - ev_completed("resp-resume"), - ]); - let resumed_mock = mount_sse_once(&server, resumed_sse).await; + let resumed_mock = mount_sse_sequence( + &server, + vec![ + sse(vec![ + ev_response_created("resp-resume-1"), + ev_assistant_message("msg-2", "Resumed turn"), + ev_completed("resp-resume-1"), + ]), + sse(vec![ + ev_response_created("resp-resume-2"), + ev_assistant_message("msg-3", "Second resumed turn"), + ev_completed("resp-resume-2"), + ]), + ], + ) + .await; let mut resume_builder = test_codex().with_config(|config| { config.model = Some("gpt-5.2-codex".to_string()); @@ -208,13 +219,139 @@ async fn resume_switches_models_preserves_base_instructions() -> Result<()> { }) .await; - let resumed_body = resumed_mock.single_request().body_json(); - let resumed_instructions = resumed_body - .get("instructions") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(); - assert_eq!(resumed_instructions, initial_instructions); + resumed + .codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "Second turn after resume".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&resumed.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let requests = resumed_mock.requests(); + assert_eq!(requests.len(), 2, "expected two resumed requests"); + + let first_resumed = &requests[0]; + assert_eq!(first_resumed.instructions_text(), initial_instructions); + let first_developer_texts = first_resumed.message_input_texts("developer"); + let first_model_switch_count = first_developer_texts + .iter() + .filter(|text| text.contains("")) + .count(); + assert!( + first_model_switch_count >= 1, + "expected model switch message on first post-resume turn" + ); + + let second_resumed = &requests[1]; + assert_eq!(second_resumed.instructions_text(), initial_instructions); + let second_developer_texts = second_resumed.message_input_texts("developer"); + let second_model_switch_count = second_developer_texts + .iter() + .filter(|text| text.contains("")) + .count(); + assert_eq!( + second_model_switch_count, 1, + "did not expect duplicate model switch message after first post-resume turn" + ); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex().with_config(|config| { + config.model = Some("gpt-5.2".to_string()); + }); + let initial = builder.build(&server).await?; + let codex = Arc::clone(&initial.codex); + let home = initial.home.clone(); + let rollout_path = initial + .session_configured + .rollout_path + .clone() + .expect("rollout path"); + + let initial_mock = mount_sse_once( + &server, + sse(vec![ + ev_response_created("resp-initial"), + ev_assistant_message("msg-1", "Completed first turn"), + ev_completed("resp-initial"), + ]), + ) + .await; + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "Record initial instructions".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await; + let _ = initial_mock.single_request(); + + let resumed_mock = mount_sse_once( + &server, + sse(vec![ + ev_response_created("resp-resume"), + ev_assistant_message("msg-2", "Resumed turn"), + ev_completed("resp-resume"), + ]), + ) + .await; + + let mut resume_builder = test_codex().with_config(|config| { + config.model = Some("gpt-5.2-codex".to_string()); + }); + let resumed = resume_builder.resume(&server, home, rollout_path).await?; + resumed + .codex + .submit(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + windows_sandbox_level: None, + model: Some("gpt-5.1-codex-max".to_string()), + effort: None, + summary: None, + collaboration_mode: None, + personality: None, + }) + .await?; + resumed + .codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "first turn after override".into(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + }) + .await?; + wait_for_event(&resumed.codex, |event| { + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + + let request = resumed_mock.single_request(); + let developer_texts = request.message_input_texts("developer"); + let model_switch_count = developer_texts + .iter() + .filter(|text| text.contains("")) + .count(); + assert_eq!(model_switch_count, 1); Ok(()) } From 7b28b350e17c8a000fb3f3fde1f4a1a5f99dd9ad Mon Sep 17 00:00:00 2001 From: Ashutosh Kumar Singh <161562995+Ashutosh0x@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:29:32 +0530 Subject: [PATCH 52/90] fix(tui): flush input buffer on init to prevent early exit on Windows (#10729) Fixes #10661. ### Problem On Windows, the sign-in menu can exit immediately if the OS-level input buffer contains trailing characters (like the Enter key from running the command). ### Solution **Flush Input Buffer on Init**: Use FlushConsoleInputBuffer on Windows (and cflush on Unix) in ui::init() to discard any input captured before the TUI was ready. Verified by @CodebyAmbrose in #10661. --- codex-rs/tui/src/tui.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index a77785c1b8..ab9c88d2ae 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -214,6 +214,8 @@ pub fn init() -> Result { } set_modes()?; + flush_terminal_input_buffer(); + set_panic_hook(); let backend = CrosstermBackend::new(stdout()); From c67120f4a08148de160dbbb044af4cbc2dcecd9e Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 10:30:18 +0000 Subject: [PATCH 53/90] fix: flaky landlock (#10689) https://openai.slack.com/archives/C095U48JNL9/p1770243347893959 --- .../suite/codex_message_processor_flow.rs | 1 + .../app-server/tests/suite/v2/turn_start.rs | 21 ++++- codex-rs/exec/tests/suite/sandbox.rs | 82 ++++++++++++++++++- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs index a041b8b0ba..2debbda653 100644 --- a/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs +++ b/codex-rs/app-server/tests/suite/codex_message_processor_flow.rs @@ -529,6 +529,7 @@ fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<() r#" model = "mock-model" approval_policy = "untrusted" +sandbox_mode = "danger-full-access" model_provider = "mock_provider" diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 87ae4cfaab..395561f69a 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -1716,11 +1716,12 @@ async fn command_execution_notifications_include_process_id() -> Result<()> { ]; let server = create_mock_responses_server_sequence(responses).await; let codex_home = TempDir::new()?; - create_config_toml( + create_config_toml_with_sandbox( codex_home.path(), &server.uri(), "never", &BTreeMap::from([(Feature::UnifiedExec, true)]), + "danger-full-access", )?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -1847,6 +1848,22 @@ fn create_config_toml( server_uri: &str, approval_policy: &str, feature_flags: &BTreeMap, +) -> std::io::Result<()> { + create_config_toml_with_sandbox( + codex_home, + server_uri, + approval_policy, + feature_flags, + "read-only", + ) +} + +fn create_config_toml_with_sandbox( + codex_home: &Path, + server_uri: &str, + approval_policy: &str, + feature_flags: &BTreeMap, + sandbox_mode: &str, ) -> std::io::Result<()> { let mut features = BTreeMap::from([(Feature::RemoteModels, false)]); for (feature, enabled) in feature_flags { @@ -1871,7 +1888,7 @@ fn create_config_toml( r#" model = "mock-model" approval_policy = "{approval_policy}" -sandbox_mode = "read-only" +sandbox_mode = "{sandbox_mode}" model_provider = "mock_provider" diff --git a/codex-rs/exec/tests/suite/sandbox.rs b/codex-rs/exec/tests/suite/sandbox.rs index 2d0ad42caa..ab8d3868d9 100644 --- a/codex-rs/exec/tests/suite/sandbox.rs +++ b/codex-rs/exec/tests/suite/sandbox.rs @@ -57,9 +57,69 @@ async fn spawn_command_under_sandbox( .await } +#[cfg(target_os = "linux")] +/// Determines whether Linux sandbox tests can run on this host. +/// +/// These tests require an enforceable filesystem sandbox. We run a tiny command +/// under the production Landlock path and skip when enforcement is unavailable +/// (for example on kernels or container profiles where Landlock is not +/// enforced). +async fn linux_sandbox_test_env() -> Option> { + let command_cwd = std::env::current_dir().ok()?; + let sandbox_cwd = command_cwd.clone(); + let policy = SandboxPolicy::ReadOnly; + + if can_apply_linux_sandbox_policy(&policy, &command_cwd, sandbox_cwd.as_path(), HashMap::new()) + .await + { + return Some(HashMap::new()); + } + + eprintln!("Skipping test: Landlock is not enforceable on this host."); + None +} + +#[cfg(target_os = "linux")] +/// Returns whether a minimal command can run successfully with the requested +/// Linux sandbox policy applied. +/// +/// This is used as a capability probe so sandbox behavior tests only run when +/// Landlock enforcement is actually active. +async fn can_apply_linux_sandbox_policy( + policy: &SandboxPolicy, + command_cwd: &Path, + sandbox_cwd: &Path, + env: HashMap, +) -> bool { + let spawn_result = spawn_command_under_sandbox( + vec!["/usr/bin/true".to_string()], + command_cwd.to_path_buf(), + policy, + sandbox_cwd, + StdioPolicy::RedirectForShellTool, + env, + ) + .await; + let Ok(mut child) = spawn_result else { + return false; + }; + child + .wait() + .await + .map(|status| status.success()) + .unwrap_or(false) +} + #[tokio::test] async fn python_multiprocessing_lock_works_under_sandbox() { core_test_support::skip_if_sandbox!(); + #[cfg(target_os = "linux")] + let sandbox_env = match linux_sandbox_test_env().await { + Some(env) => env, + None => return, + }; + #[cfg(not(target_os = "linux"))] + let sandbox_env = HashMap::new(); #[cfg(target_os = "macos")] let writable_roots = Vec::::new(); @@ -103,7 +163,7 @@ if __name__ == '__main__': &policy, sandbox_cwd.as_path(), StdioPolicy::Inherit, - HashMap::new(), + sandbox_env, ) .await .expect("should be able to spawn python under sandbox"); @@ -115,6 +175,13 @@ if __name__ == '__main__': #[tokio::test] async fn python_getpwuid_works_under_sandbox() { core_test_support::skip_if_sandbox!(); + #[cfg(target_os = "linux")] + let sandbox_env = match linux_sandbox_test_env().await { + Some(env) => env, + None => return, + }; + #[cfg(not(target_os = "linux"))] + let sandbox_env = HashMap::new(); if std::process::Command::new("python3") .arg("--version") @@ -139,7 +206,7 @@ async fn python_getpwuid_works_under_sandbox() { &policy, sandbox_cwd.as_path(), StdioPolicy::RedirectForShellTool, - HashMap::new(), + sandbox_env, ) .await .expect("should be able to spawn python under sandbox"); @@ -154,6 +221,13 @@ async fn python_getpwuid_works_under_sandbox() { #[tokio::test] async fn sandbox_distinguishes_command_and_policy_cwds() { core_test_support::skip_if_sandbox!(); + #[cfg(target_os = "linux")] + let sandbox_env = match linux_sandbox_test_env().await { + Some(env) => env, + None => return, + }; + #[cfg(not(target_os = "linux"))] + let sandbox_env = HashMap::new(); let temp = tempfile::tempdir().expect("should be able to create temp dir"); let sandbox_root = temp.path().join("sandbox"); let command_root = temp.path().join("command"); @@ -187,7 +261,7 @@ async fn sandbox_distinguishes_command_and_policy_cwds() { &policy, canonical_sandbox_root.as_path(), StdioPolicy::Inherit, - HashMap::new(), + sandbox_env.clone(), ) .await .expect("should spawn command writing to forbidden path"); @@ -218,7 +292,7 @@ async fn sandbox_distinguishes_command_and_policy_cwds() { &policy, canonical_sandbox_root.as_path(), StdioPolicy::Inherit, - HashMap::new(), + sandbox_env, ) .await .expect("should spawn command writing to sandbox root"); From 97582ac52d4a0106e455aace07d13f2b79cd5646 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 11:11:00 +0000 Subject: [PATCH 54/90] Allow user shell commands to run alongside active turns (#10513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary - refactor user shell command execution into a shared helper and add modes for standalone vs active-turn execution - run user shell commands asynchronously when a turn is already active so they don’t replace or abort the current turn - extend the tests to cover the new behavior and add the generated Codex environment manifest Testing - Not run (not requested) --- codex-rs/core/src/codex.rs | 75 ++++ codex-rs/core/src/tasks/mod.rs | 25 +- codex-rs/core/src/tasks/user_shell.rs | 421 +++++++++++--------- codex-rs/core/tests/suite/user_shell_cmd.rs | 114 ++++++ 4 files changed, 450 insertions(+), 185 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 8567c9286a..dc7d5902d6 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1686,6 +1686,17 @@ impl Session { .map(|task| Arc::clone(&task.turn_context)) } + async fn active_turn_context_and_cancellation_token( + &self, + ) -> Option<(Arc, CancellationToken)> { + let active = self.active_turn.lock().await; + let (_, task) = active.as_ref()?.tasks.first()?; + Some(( + Arc::clone(&task.turn_context), + task.cancellation_token.child_token(), + )) + } + pub(crate) async fn record_execpolicy_amendment_message( &self, sub_id: &str, @@ -2716,7 +2727,9 @@ mod handlers { use crate::tasks::CompactTask; use crate::tasks::RegularTask; use crate::tasks::UndoTask; + use crate::tasks::UserShellCommandMode; use crate::tasks::UserShellCommandTask; + use crate::tasks::execute_user_shell_command; use codex_protocol::custom_prompts::CustomPrompt; use codex_protocol::protocol::CodexErrorInfo; use codex_protocol::protocol::ErrorEvent; @@ -2863,6 +2876,23 @@ mod handlers { command: String, previous_context: &mut Option>, ) { + if let Some((turn_context, cancellation_token)) = + sess.active_turn_context_and_cancellation_token().await + { + let session = Arc::clone(sess); + tokio::spawn(async move { + execute_user_shell_command( + session, + turn_context, + command, + cancellation_token, + UserShellCommandMode::ActiveTurnAuxiliary, + ) + .await; + }); + return; + } + let turn_context = sess.new_default_turn_with_sub_id(sub_id).await; sess.spawn_task( Arc::clone(&turn_context), @@ -4807,6 +4837,7 @@ mod tests { use codex_app_server_protocol::AuthMode; use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; + use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use std::path::Path; use std::time::Duration; @@ -6044,6 +6075,50 @@ mod tests { assert!(rx.try_recv().is_err()); } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn task_finish_persists_leftover_pending_input() { + let (sess, tc, _rx) = make_session_and_context_with_rx().await; + let input = vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }]; + sess.spawn_task( + Arc::clone(&tc), + input, + NeverEndingTask { + kind: TaskKind::Regular, + listen_to_cancellation_token: false, + }, + ) + .await; + + sess.inject_response_items(vec![ResponseInputItem::Message { + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "late pending input".to_string(), + }], + }]) + .await + .expect("inject pending input into active turn"); + + sess.on_task_finished(Arc::clone(&tc), None).await; + + let history = sess.clone_history().await; + let expected = ResponseItem::Message { + id: None, + role: "user".to_string(), + content: vec![ContentItem::InputText { + text: "late pending input".to_string(), + }], + end_turn: None, + phase: None, + }; + assert!( + history.raw_items().iter().any(|item| item == &expected), + "expected pending input to be persisted into history on turn completion" + ); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn abort_review_task_emits_exited_then_aborted_and_records_history() { let (sess, tc, rx) = make_session_and_context_with_rx().await; diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index 0ab6e2f49b..0821ba7bc2 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -31,6 +31,7 @@ use crate::state::ActiveTurn; use crate::state::RunningTask; use crate::state::TaskKind; use codex_protocol::models::ContentItem; +use codex_protocol::models::ResponseInputItem; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::RolloutItem; use codex_protocol::user_input::UserInput; @@ -40,7 +41,9 @@ pub(crate) use ghost_snapshot::GhostSnapshotTask; pub(crate) use regular::RegularTask; pub(crate) use review::ReviewTask; pub(crate) use undo::UndoTask; +pub(crate) use user_shell::UserShellCommandMode; pub(crate) use user_shell::UserShellCommandTask; +pub(crate) use user_shell::execute_user_shell_command; const GRACEFULL_INTERRUPTION_TIMEOUT_MS: u64 = 100; const TURN_ABORTED_INTERRUPTED_GUIDANCE: &str = "The user interrupted the previous turn on purpose. If any tools/commands were aborted, they may have partially executed; verify current state before retrying."; @@ -187,15 +190,27 @@ impl Session { last_agent_message: Option, ) { let mut active = self.active_turn.lock().await; - let should_close_processes = if let Some(at) = active.as_mut() + let mut pending_input = Vec::::new(); + let mut should_close_processes = false; + if let Some(at) = active.as_mut() && at.remove_task(&turn_context.sub_id) { + let mut ts = at.turn_state.lock().await; + pending_input = ts.take_pending_input(); + should_close_processes = true; + } + if should_close_processes { *active = None; - true - } else { - false - }; + } drop(active); + if !pending_input.is_empty() { + let pending_response_items = pending_input + .into_iter() + .map(ResponseItem::from) + .collect::>(); + self.record_conversation_items(turn_context.as_ref(), &pending_response_items) + .await; + } if should_close_processes { self.close_unified_exec_processes().await; } diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index 6c37cb150e..d626057d92 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -32,9 +32,22 @@ use crate::user_shell_command::user_shell_command_record_item; use super::SessionTask; use super::SessionTaskContext; +use crate::codex::Session; +use codex_protocol::models::ResponseInputItem; +use codex_protocol::models::ResponseItem; const USER_SHELL_TIMEOUT_MS: u64 = 60 * 60 * 1000; // 1 hour +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum UserShellCommandMode { + /// Executes as an independent turn lifecycle (emits TurnStarted/TurnComplete + /// via task lifecycle plumbing). + StandaloneTurn, + /// Executes while another turn is already active. This mode must not emit a + /// second TurnStarted/TurnComplete pair for the same active turn. + ActiveTurnAuxiliary, +} + #[derive(Clone)] pub(crate) struct UserShellCommandTask { command: String, @@ -59,198 +72,246 @@ impl SessionTask for UserShellCommandTask { _input: Vec, cancellation_token: CancellationToken, ) -> Option { - let _ = session - .session - .services - .otel_manager - .counter("codex.task.user_shell", 1, &[]); + execute_user_shell_command( + session.clone_session(), + turn_context, + self.command.clone(), + cancellation_token, + UserShellCommandMode::StandaloneTurn, + ) + .await; + None + } +} +pub(crate) async fn execute_user_shell_command( + session: Arc, + turn_context: Arc, + command: String, + cancellation_token: CancellationToken, + mode: UserShellCommandMode, +) { + session + .services + .otel_manager + .counter("codex.task.user_shell", 1, &[]); + + if mode == UserShellCommandMode::StandaloneTurn { + // Auxiliary mode runs within an existing active turn. That turn already + // emitted TurnStarted, so emitting another TurnStarted here would create + // duplicate turn lifecycle events and confuse clients. let event = EventMsg::TurnStarted(TurnStartedEvent { model_context_window: turn_context.model_context_window(), collaboration_mode_kind: turn_context.collaboration_mode.mode, }); - let session = session.clone_session(); session.send_event(turn_context.as_ref(), event).await; + } - // Execute the user's script under their default shell when known; this - // allows commands that use shell features (pipes, &&, redirects, etc.). - // We do not source rc files or otherwise reformat the script. - let use_login_shell = true; - let session_shell = session.user_shell(); - let display_command = session_shell.derive_exec_args(&self.command, use_login_shell); - let exec_command = - maybe_wrap_shell_lc_with_snapshot(&display_command, session_shell.as_ref()); + // Execute the user's script under their default shell when known; this + // allows commands that use shell features (pipes, &&, redirects, etc.). + // We do not source rc files or otherwise reformat the script. + let use_login_shell = true; + let session_shell = session.user_shell(); + let display_command = session_shell.derive_exec_args(&command, use_login_shell); + let exec_command = maybe_wrap_shell_lc_with_snapshot(&display_command, session_shell.as_ref()); - let call_id = Uuid::new_v4().to_string(); - let raw_command = self.command.clone(); - let cwd = turn_context.cwd.clone(); + let call_id = Uuid::new_v4().to_string(); + let raw_command = command; + let cwd = turn_context.cwd.clone(); - let parsed_cmd = parse_command(&display_command); - session - .send_event( + let parsed_cmd = parse_command(&display_command); + session + .send_event( + turn_context.as_ref(), + EventMsg::ExecCommandBegin(ExecCommandBeginEvent { + call_id: call_id.clone(), + process_id: None, + turn_id: turn_context.sub_id.clone(), + command: display_command.clone(), + cwd: cwd.clone(), + parsed_cmd: parsed_cmd.clone(), + source: ExecCommandSource::UserShell, + interaction_input: None, + }), + ) + .await; + + let exec_env = ExecEnv { + command: exec_command.clone(), + cwd: cwd.clone(), + env: create_env( + &turn_context.shell_environment_policy, + Some(session.conversation_id), + ), + // TODO(zhao-oai): Now that we have ExecExpiration::Cancellation, we + // should use that instead of an "arbitrarily large" timeout here. + expiration: USER_SHELL_TIMEOUT_MS.into(), + sandbox: SandboxType::None, + windows_sandbox_level: turn_context.windows_sandbox_level, + sandbox_permissions: SandboxPermissions::UseDefault, + justification: None, + arg0: None, + }; + + let stdout_stream = Some(StdoutStream { + sub_id: turn_context.sub_id.clone(), + call_id: call_id.clone(), + tx_event: session.get_tx_event(), + }); + + let sandbox_policy = SandboxPolicy::DangerFullAccess; + let exec_result = execute_exec_env(exec_env, &sandbox_policy, stdout_stream) + .or_cancel(&cancellation_token) + .await; + + match exec_result { + Err(CancelErr::Cancelled) => { + let aborted_message = "command aborted by user".to_string(); + let exec_output = ExecToolCallOutput { + exit_code: -1, + stdout: StreamOutput::new(String::new()), + stderr: StreamOutput::new(aborted_message.clone()), + aggregated_output: StreamOutput::new(aborted_message.clone()), + duration: Duration::ZERO, + timed_out: false, + }; + persist_user_shell_output( + &session, turn_context.as_ref(), - EventMsg::ExecCommandBegin(ExecCommandBeginEvent { - call_id: call_id.clone(), - process_id: None, - turn_id: turn_context.sub_id.clone(), - command: display_command.clone(), - cwd: cwd.clone(), - parsed_cmd: parsed_cmd.clone(), - source: ExecCommandSource::UserShell, - interaction_input: None, - }), + &raw_command, + &exec_output, + mode, + ) + .await; + session + .send_event( + turn_context.as_ref(), + EventMsg::ExecCommandEnd(ExecCommandEndEvent { + call_id, + process_id: None, + turn_id: turn_context.sub_id.clone(), + command: display_command.clone(), + cwd: cwd.clone(), + parsed_cmd: parsed_cmd.clone(), + source: ExecCommandSource::UserShell, + interaction_input: None, + stdout: String::new(), + stderr: aborted_message.clone(), + aggregated_output: aborted_message.clone(), + exit_code: -1, + duration: Duration::ZERO, + formatted_output: aborted_message, + }), + ) + .await; + } + Ok(Ok(output)) => { + session + .send_event( + turn_context.as_ref(), + EventMsg::ExecCommandEnd(ExecCommandEndEvent { + call_id: call_id.clone(), + process_id: None, + turn_id: turn_context.sub_id.clone(), + command: display_command.clone(), + cwd: cwd.clone(), + parsed_cmd: parsed_cmd.clone(), + source: ExecCommandSource::UserShell, + interaction_input: None, + stdout: output.stdout.text.clone(), + stderr: output.stderr.text.clone(), + aggregated_output: output.aggregated_output.text.clone(), + exit_code: output.exit_code, + duration: output.duration, + formatted_output: format_exec_output_str( + &output, + turn_context.truncation_policy, + ), + }), + ) + .await; + + persist_user_shell_output(&session, turn_context.as_ref(), &raw_command, &output, mode) + .await; + } + Ok(Err(err)) => { + error!("user shell command failed: {err:?}"); + let message = format!("execution error: {err:?}"); + let exec_output = ExecToolCallOutput { + exit_code: -1, + stdout: StreamOutput::new(String::new()), + stderr: StreamOutput::new(message.clone()), + aggregated_output: StreamOutput::new(message.clone()), + duration: Duration::ZERO, + timed_out: false, + }; + session + .send_event( + turn_context.as_ref(), + EventMsg::ExecCommandEnd(ExecCommandEndEvent { + call_id, + process_id: None, + turn_id: turn_context.sub_id.clone(), + command: display_command, + cwd, + parsed_cmd, + source: ExecCommandSource::UserShell, + interaction_input: None, + stdout: exec_output.stdout.text.clone(), + stderr: exec_output.stderr.text.clone(), + aggregated_output: exec_output.aggregated_output.text.clone(), + exit_code: exec_output.exit_code, + duration: exec_output.duration, + formatted_output: format_exec_output_str( + &exec_output, + turn_context.truncation_policy, + ), + }), + ) + .await; + persist_user_shell_output( + &session, + turn_context.as_ref(), + &raw_command, + &exec_output, + mode, ) .await; - - let exec_env = ExecEnv { - command: exec_command.clone(), - cwd: cwd.clone(), - env: create_env( - &turn_context.shell_environment_policy, - Some(session.conversation_id), - ), - // TODO(zhao-oai): Now that we have ExecExpiration::Cancellation, we - // should use that instead of an "arbitrarily large" timeout here. - expiration: USER_SHELL_TIMEOUT_MS.into(), - sandbox: SandboxType::None, - windows_sandbox_level: turn_context.windows_sandbox_level, - sandbox_permissions: SandboxPermissions::UseDefault, - justification: None, - arg0: None, - }; - - let stdout_stream = Some(StdoutStream { - sub_id: turn_context.sub_id.clone(), - call_id: call_id.clone(), - tx_event: session.get_tx_event(), - }); - - let sandbox_policy = SandboxPolicy::DangerFullAccess; - let exec_result = execute_exec_env(exec_env, &sandbox_policy, stdout_stream) - .or_cancel(&cancellation_token) - .await; - - match exec_result { - Err(CancelErr::Cancelled) => { - let aborted_message = "command aborted by user".to_string(); - let exec_output = ExecToolCallOutput { - exit_code: -1, - stdout: StreamOutput::new(String::new()), - stderr: StreamOutput::new(aborted_message.clone()), - aggregated_output: StreamOutput::new(aborted_message.clone()), - duration: Duration::ZERO, - timed_out: false, - }; - let output_items = [user_shell_command_record_item( - &raw_command, - &exec_output, - &turn_context, - )]; - session - .record_conversation_items(turn_context.as_ref(), &output_items) - .await; - session - .send_event( - turn_context.as_ref(), - EventMsg::ExecCommandEnd(ExecCommandEndEvent { - call_id, - process_id: None, - turn_id: turn_context.sub_id.clone(), - command: display_command.clone(), - cwd: cwd.clone(), - parsed_cmd: parsed_cmd.clone(), - source: ExecCommandSource::UserShell, - interaction_input: None, - stdout: String::new(), - stderr: aborted_message.clone(), - aggregated_output: aborted_message.clone(), - exit_code: -1, - duration: Duration::ZERO, - formatted_output: aborted_message, - }), - ) - .await; - } - Ok(Ok(output)) => { - session - .send_event( - turn_context.as_ref(), - EventMsg::ExecCommandEnd(ExecCommandEndEvent { - call_id: call_id.clone(), - process_id: None, - turn_id: turn_context.sub_id.clone(), - command: display_command.clone(), - cwd: cwd.clone(), - parsed_cmd: parsed_cmd.clone(), - source: ExecCommandSource::UserShell, - interaction_input: None, - stdout: output.stdout.text.clone(), - stderr: output.stderr.text.clone(), - aggregated_output: output.aggregated_output.text.clone(), - exit_code: output.exit_code, - duration: output.duration, - formatted_output: format_exec_output_str( - &output, - turn_context.truncation_policy, - ), - }), - ) - .await; - - let output_items = [user_shell_command_record_item( - &raw_command, - &output, - &turn_context, - )]; - session - .record_conversation_items(turn_context.as_ref(), &output_items) - .await; - } - Ok(Err(err)) => { - error!("user shell command failed: {err:?}"); - let message = format!("execution error: {err:?}"); - let exec_output = ExecToolCallOutput { - exit_code: -1, - stdout: StreamOutput::new(String::new()), - stderr: StreamOutput::new(message.clone()), - aggregated_output: StreamOutput::new(message.clone()), - duration: Duration::ZERO, - timed_out: false, - }; - session - .send_event( - turn_context.as_ref(), - EventMsg::ExecCommandEnd(ExecCommandEndEvent { - call_id, - process_id: None, - turn_id: turn_context.sub_id.clone(), - command: display_command, - cwd, - parsed_cmd, - source: ExecCommandSource::UserShell, - interaction_input: None, - stdout: exec_output.stdout.text.clone(), - stderr: exec_output.stderr.text.clone(), - aggregated_output: exec_output.aggregated_output.text.clone(), - exit_code: exec_output.exit_code, - duration: exec_output.duration, - formatted_output: format_exec_output_str( - &exec_output, - turn_context.truncation_policy, - ), - }), - ) - .await; - let output_items = [user_shell_command_record_item( - &raw_command, - &exec_output, - &turn_context, - )]; - session - .record_conversation_items(turn_context.as_ref(), &output_items) - .await; - } } - None + } +} + +async fn persist_user_shell_output( + session: &Session, + turn_context: &TurnContext, + raw_command: &str, + exec_output: &ExecToolCallOutput, + mode: UserShellCommandMode, +) { + let output_item = user_shell_command_record_item(raw_command, exec_output, turn_context); + + if mode == UserShellCommandMode::StandaloneTurn { + session + .record_conversation_items(turn_context, std::slice::from_ref(&output_item)) + .await; + return; + } + + let response_input_item = match output_item { + ResponseItem::Message { role, content, .. } => ResponseInputItem::Message { role, content }, + _ => unreachable!("user shell command output record should always be a message"), + }; + + if let Err(items) = session + .inject_response_items(vec![response_input_item]) + .await + { + let response_items = items + .into_iter() + .map(ResponseItem::from) + .collect::>(); + session + .record_conversation_items(turn_context, &response_items) + .await; } } diff --git a/codex-rs/core/tests/suite/user_shell_cmd.rs b/codex-rs/core/tests/suite/user_shell_cmd.rs index 45c91126d1..88a5caba4c 100644 --- a/codex-rs/core/tests/suite/user_shell_cmd.rs +++ b/codex-rs/core/tests/suite/user_shell_cmd.rs @@ -1,5 +1,6 @@ use anyhow::Context; use codex_core::features::Feature; +use codex_core::protocol::AskForApproval; use codex_core::protocol::EventMsg; use codex_core::protocol::ExecCommandEndEvent; use codex_core::protocol::ExecCommandSource; @@ -7,6 +8,8 @@ use codex_core::protocol::ExecOutputStream; use codex_core::protocol::Op; use codex_core::protocol::SandboxPolicy; use codex_core::protocol::TurnAbortReason; +use codex_protocol::config_types::ReasoningSummary; +use codex_protocol::user_input::UserInput; use core_test_support::assert_regex_match; use core_test_support::responses; use core_test_support::responses::ev_assistant_message; @@ -23,6 +26,8 @@ use core_test_support::wait_for_event_match; use regex_lite::escape; use std::path::PathBuf; use tempfile::TempDir; +use tokio::time::Duration; +use tokio::time::timeout; #[tokio::test] async fn user_shell_cmd_ls_and_cat_in_temp_dir() { @@ -119,6 +124,115 @@ async fn user_shell_cmd_can_be_interrupted() { assert_eq!(ev.reason, TurnAbortReason::Interrupted); } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn user_shell_command_does_not_replace_active_turn() -> anyhow::Result<()> { + let server = start_mock_server().await; + let mut builder = test_codex().with_model("gpt-5.1"); + let fixture = builder.build(&server).await?; + + let call_id = "active-turn-shell-call"; + let args = if cfg!(windows) { + serde_json::json!({ + "command": "Start-Sleep -Seconds 2; Write-Output model-shell", + "timeout_ms": 10_000, + }) + } else { + serde_json::json!({ + "command": "sleep 2; echo model-shell", + "timeout_ms": 10_000, + }) + }; + let first = sse(vec![ + ev_response_created("resp-1"), + ev_function_call(call_id, "shell_command", &serde_json::to_string(&args)?), + ev_completed("resp-1"), + ]); + let second = sse(vec![ + ev_assistant_message("msg-1", "done"), + ev_completed("resp-2"), + ]); + let mock = responses::mount_sse_sequence(&server, vec![first, second]).await; + + fixture + .codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "run model shell command".to_string(), + text_elements: Vec::new(), + }], + final_output_json_schema: None, + cwd: fixture.cwd.path().to_path_buf(), + approval_policy: AskForApproval::Never, + sandbox_policy: SandboxPolicy::DangerFullAccess, + model: fixture.session_configured.model.clone(), + effort: None, + summary: ReasoningSummary::Auto, + collaboration_mode: None, + personality: None, + }) + .await?; + + let _ = wait_for_event_match(&fixture.codex, |ev| match ev { + EventMsg::ExecCommandBegin(event) if event.source == ExecCommandSource::Agent => { + Some(event.clone()) + } + _ => None, + }) + .await; + + #[cfg(windows)] + let user_shell_command = "Write-Output user-shell".to_string(); + #[cfg(not(windows))] + let user_shell_command = "printf user-shell".to_string(); + fixture + .codex + .submit(Op::RunUserShellCommand { + command: user_shell_command, + }) + .await?; + + let mut saw_replaced_abort = false; + let mut saw_user_shell_end = false; + let mut saw_turn_complete = false; + for _ in 0..200 { + let event = timeout(Duration::from_secs(20), fixture.codex.next_event()) + .await + .context("timed out waiting for event")? + .context("event stream ended unexpectedly")?; + match event.msg { + EventMsg::TurnAborted(ev) if ev.reason == TurnAbortReason::Replaced => { + saw_replaced_abort = true; + } + EventMsg::ExecCommandEnd(ev) if ev.source == ExecCommandSource::UserShell => { + saw_user_shell_end = true; + } + EventMsg::TurnComplete(_) => { + saw_turn_complete = true; + break; + } + _ => {} + } + } + + assert!(saw_turn_complete, "expected turn to complete"); + assert!( + saw_user_shell_end, + "expected user shell command to finish while turn was active" + ); + assert!( + !saw_replaced_abort, + "user shell command should not replace the active turn" + ); + + assert_eq!( + mock.requests().len(), + 2, + "active turn should continue and issue the follow-up model request" + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyhow::Result<()> { let server = responses::start_mock_server().await; From aa46b5cf99327a84c0533527eecd3766beb9fccd Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 12:30:16 +0000 Subject: [PATCH 55/90] nit: backfill stronger (#10738) --- codex-rs/core/src/rollout/metadata.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/rollout/metadata.rs b/codex-rs/core/src/rollout/metadata.rs index 42e52f78d6..ec11ca3249 100644 --- a/codex-rs/core/src/rollout/metadata.rs +++ b/codex-rs/core/src/rollout/metadata.rs @@ -276,9 +276,28 @@ async fn collect_rollout_paths(root: &Path) -> std::io::Result> { continue; } }; - while let Some(entry) = read_dir.next_entry().await? { + loop { + let next_entry = match read_dir.next_entry().await { + Ok(next_entry) => next_entry, + Err(err) => { + warn!( + "failed to read directory entry under {}: {err}", + dir.display() + ); + continue; + } + }; + let Some(entry) = next_entry else { + break; + }; let path = entry.path(); - let file_type = entry.file_type().await?; + let file_type = match entry.file_type().await { + Ok(file_type) => file_type, + Err(err) => { + warn!("failed to read file type for {}: {err}", path.display()); + continue; + } + }; if file_type.is_dir() { stack.push(path); continue; From b2424cb635094fbd439f15aa755e862fe03986b6 Mon Sep 17 00:00:00 2001 From: pap-openai Date: Thu, 5 Feb 2026 13:24:55 +0000 Subject: [PATCH 56/90] adding fork information (UI) when forking (#10246) - shows `/fork` command that ran in prev session - shows `session forked from name (uuid) || uuid (if name is not set)` as an event in new session --- codex-rs/core/src/lib.rs | 1 + codex-rs/core/src/rollout/mod.rs | 1 + codex-rs/tui/src/app.rs | 2 + codex-rs/tui/src/chatwidget.rs | 49 ++++++++++++++ ...et__tests__forked_thread_history_line.snap | 5 ++ ...rked_thread_history_line_without_name.snap | 5 ++ codex-rs/tui/src/chatwidget/tests.rs | 64 +++++++++++++++++++ 7 files changed, 127 insertions(+) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line.snap create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line_without_name.snap diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 50a1f85387..7b69c4370a 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -110,6 +110,7 @@ pub use rollout::SessionMeta; pub use rollout::find_archived_thread_path_by_id_str; #[deprecated(note = "use find_thread_path_by_id_str")] pub use rollout::find_conversation_path_by_id_str; +pub use rollout::find_thread_name_by_id; pub use rollout::find_thread_path_by_id_str; pub use rollout::find_thread_path_by_name_str; pub use rollout::list::Cursor; diff --git a/codex-rs/core/src/rollout/mod.rs b/codex-rs/core/src/rollout/mod.rs index 60775d04b0..5e6bd9bbff 100644 --- a/codex-rs/core/src/rollout/mod.rs +++ b/codex-rs/core/src/rollout/mod.rs @@ -24,6 +24,7 @@ pub use list::find_thread_path_by_id_str as find_conversation_path_by_id_str; pub use list::rollout_date_parts; pub use recorder::RolloutRecorder; pub use recorder::RolloutRecorderParams; +pub use session_index::find_thread_name_by_id; pub use session_index::find_thread_path_by_name_str; #[cfg(test)] diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 99164efa61..e2d35dc08f 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1409,6 +1409,8 @@ impl App { self.chat_widget.thread_id(), self.chat_widget.thread_name(), ); + self.chat_widget + .add_plain_history_lines(vec!["/fork".magenta().into()]); if let Some(path) = self.chat_widget.rollout_path() { match self .server diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index ee8400bbf0..bbe6321a3b 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -37,6 +37,7 @@ use codex_core::config::ConstraintResult; use codex_core::config::types::Notifications; use codex_core::features::FEATURES; use codex_core::features::Feature; +use codex_core::find_thread_name_by_id; use codex_core::git_info::current_branch_name; use codex_core::git_info::local_git_branches; use codex_core::models_manager::manager::ModelsManager; @@ -808,6 +809,7 @@ impl ChatWidget { self.forked_from = event.forked_from_id; self.current_rollout_path = event.rollout_path.clone(); let initial_messages = event.initial_messages.clone(); + let forked_from_id = event.forked_from_id; let model_for_header = event.model.clone(); self.session_header.set_model(&model_for_header); self.current_collaboration_mode = self.current_collaboration_mode.with_updates( @@ -843,11 +845,58 @@ impl ChatWidget { if let Some(user_message) = self.initial_user_message.take() { self.submit_user_message(user_message); } + if let Some(forked_from_id) = forked_from_id { + self.emit_forked_thread_event(forked_from_id); + } if !self.suppress_session_configured_redraw { self.request_redraw(); } } + fn emit_forked_thread_event(&self, forked_from_id: ThreadId) { + let app_event_tx = self.app_event_tx.clone(); + let codex_home = self.config.codex_home.clone(); + tokio::spawn(async move { + let forked_from_id_text = forked_from_id.to_string(); + let send_name_and_id = |name: String| { + let line: Line<'static> = vec![ + "• ".dim(), + "Thread forked from ".into(), + name.cyan(), + " (".into(), + forked_from_id_text.clone().cyan(), + ")".into(), + ] + .into(); + app_event_tx.send(AppEvent::InsertHistoryCell(Box::new( + PlainHistoryCell::new(vec![line]), + ))); + }; + let send_id_only = || { + let line: Line<'static> = vec![ + "• ".dim(), + "Thread forked from ".into(), + forked_from_id_text.clone().cyan(), + ] + .into(); + app_event_tx.send(AppEvent::InsertHistoryCell(Box::new( + PlainHistoryCell::new(vec![line]), + ))); + }; + + match find_thread_name_by_id(&codex_home, &forked_from_id).await { + Ok(Some(name)) if !name.trim().is_empty() => { + send_name_and_id(name); + } + Ok(_) => send_id_only(), + Err(err) => { + tracing::warn!("Failed to read forked thread name: {err}"); + send_id_only(); + } + } + }); + } + fn on_thread_name_updated(&mut self, event: codex_core::protocol::ThreadNameUpdatedEvent) { if self.thread_id == Some(event.thread_id) { self.thread_name = event.thread_name; diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line.snap new file mode 100644 index 0000000000..d089f59639 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: combined +--- +• Thread forked from named-thread (e9f18a88-8081-4e51-9d4e-8af5cde2d8dd) diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line_without_name.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line_without_name.snap new file mode 100644 index 0000000000..f25eb53645 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__forked_thread_history_line_without_name.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: combined +--- +• Thread forked from 019c2d47-4935-7423-a190-05691f566092 diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index ba28160099..90b275b157 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -246,6 +246,70 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() { assert_eq!(stored_images, local_images); } +#[tokio::test] +async fn forked_thread_history_line_includes_name_and_id_snapshot() { + let (chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + let mut chat = chat; + let temp = tempdir().expect("tempdir"); + chat.config.codex_home = temp.path().to_path_buf(); + + let forked_from_id = + ThreadId::from_string("e9f18a88-8081-4e51-9d4e-8af5cde2d8dd").expect("forked id"); + let session_index_entry = format!( + "{{\"id\":\"{forked_from_id}\",\"thread_name\":\"named-thread\",\"updated_at\":\"2024-01-02T00:00:00Z\"}}\n" + ); + std::fs::write(temp.path().join("session_index.jsonl"), session_index_entry) + .expect("write session index"); + + chat.emit_forked_thread_event(forked_from_id); + + let history_cell = tokio::time::timeout(std::time::Duration::from_secs(2), async { + loop { + match rx.recv().await { + Some(AppEvent::InsertHistoryCell(cell)) => break cell, + Some(_) => continue, + None => panic!("app event channel closed before forked thread history was emitted"), + } + } + }) + .await + .expect("timed out waiting for forked thread history"); + let combined = lines_to_single_string(&history_cell.display_lines(80)); + + assert!( + combined.contains("Thread forked from"), + "expected forked thread message in history" + ); + assert_snapshot!("forked_thread_history_line", combined); +} + +#[tokio::test] +async fn forked_thread_history_line_without_name_shows_id_once_snapshot() { + let (chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + let mut chat = chat; + let temp = tempdir().expect("tempdir"); + chat.config.codex_home = temp.path().to_path_buf(); + + let forked_from_id = + ThreadId::from_string("019c2d47-4935-7423-a190-05691f566092").expect("forked id"); + chat.emit_forked_thread_event(forked_from_id); + + let history_cell = tokio::time::timeout(std::time::Duration::from_secs(2), async { + loop { + match rx.recv().await { + Some(AppEvent::InsertHistoryCell(cell)) => break cell, + Some(_) => continue, + None => panic!("app event channel closed before forked thread history was emitted"), + } + } + }) + .await + .expect("timed out waiting for forked thread history"); + let combined = lines_to_single_string(&history_cell.display_lines(80)); + + assert_snapshot!("forked_thread_history_line_without_name", combined); +} + #[tokio::test] async fn submission_preserves_text_elements_and_local_images() { let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; From 040ecee7154556924e3b66b4b52253a51afc731f Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 13:51:53 +0000 Subject: [PATCH 57/90] Update explorer role default model (#10748) Summary - switch the explorer role in core agent configuration to use `gpt-5.1-codex-mini` as the default model override - leave other role defaults untouched Testing - Not run (not requested) --- codex-rs/core/src/agent/role.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/src/agent/role.rs b/codex-rs/core/src/agent/role.rs index 76e3a373ed..74eca4179d 100644 --- a/codex-rs/core/src/agent/role.rs +++ b/codex-rs/core/src/agent/role.rs @@ -8,7 +8,7 @@ use serde::Serialize; const ORCHESTRATOR_PROMPT: &str = include_str!("../../templates/agents/orchestrator.md"); /// Default model override used. // TODO(jif) update when we have something smarter. -const EXPLORER_MODEL: &str = "gpt-5.2-codex"; +const EXPLORER_MODEL: &str = "gpt-5.1-codex-mini"; /// Enumerated list of all supported agent roles. const ALL_ROLES: [AgentRole; 3] = [ From f2ffc4e5d0520dc476de28a1d12028f5fff6a893 Mon Sep 17 00:00:00 2001 From: iceweasel-oai Date: Thu, 5 Feb 2026 06:30:31 -0800 Subject: [PATCH 58/90] Include real OS info in metrics. (#10425) calculated a hashed user ID from either auth user id or API key Also correctly populates OS. These will make our metrics more useful and powerful for analysis. --- codex-rs/Cargo.lock | 5 +++ codex-rs/otel/Cargo.toml | 2 + codex-rs/otel/src/lib.rs | 3 +- codex-rs/otel/src/metrics/client.rs | 30 +++++++++++-- codex-rs/utils/string/Cargo.toml | 3 ++ codex-rs/utils/string/src/lib.rs | 43 +++++++++++++++++++ codex-rs/windows-sandbox-rs/src/lib.rs | 2 +- .../windows-sandbox-rs/src/setup_error.rs | 32 +++----------- 8 files changed, 88 insertions(+), 32 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index ebc7ddd33c..4e1758355f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1920,6 +1920,7 @@ dependencies = [ "codex-app-server-protocol", "codex-protocol", "codex-utils-absolute-path", + "codex-utils-string", "eventsource-stream", "http 1.4.0", "opentelemetry", @@ -1927,6 +1928,7 @@ dependencies = [ "opentelemetry-otlp", "opentelemetry-semantic-conventions", "opentelemetry_sdk", + "os_info", "pretty_assertions", "reqwest", "serde", @@ -2244,6 +2246,9 @@ dependencies = [ [[package]] name = "codex-utils-string" version = "0.0.0" +dependencies = [ + "pretty_assertions", +] [[package]] name = "codex-windows-sandbox" diff --git a/codex-rs/otel/Cargo.toml b/codex-rs/otel/Cargo.toml index 0fbd2a8055..c0bcb365b6 100644 --- a/codex-rs/otel/Cargo.toml +++ b/codex-rs/otel/Cargo.toml @@ -23,6 +23,7 @@ disable-default-metrics-exporter = [] chrono = { workspace = true } codex-app-server-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } +codex-utils-string = { workspace = true } codex-api = { workspace = true } codex-protocol = { workspace = true } eventsource-stream = { workspace = true } @@ -50,6 +51,7 @@ opentelemetry_sdk = { workspace = true, features = [ "trace", ] } http = { workspace = true } +os_info = { workspace = true } reqwest = { workspace = true, features = ["blocking", "rustls-tls"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/otel/src/lib.rs b/codex-rs/otel/src/lib.rs index aed9e15b8e..ed6e5a1c9d 100644 --- a/codex-rs/otel/src/lib.rs +++ b/codex-rs/otel/src/lib.rs @@ -14,6 +14,7 @@ use crate::metrics::validation::validate_tag_key; use crate::metrics::validation::validate_tag_value; use crate::otel_provider::OtelProvider; use codex_protocol::ThreadId; +pub use codex_utils_string::sanitize_metric_tag_value; use opentelemetry_sdk::metrics::data::ResourceMetrics; use serde::Serialize; use std::time::Duration; @@ -188,7 +189,7 @@ impl OtelManager { if !self.metrics_use_metadata_tags { return Ok(Vec::new()); } - let mut tags = Vec::with_capacity(6); + let mut tags = Vec::with_capacity(5); Self::push_metadata_tag(&mut tags, "auth_mode", self.metadata.auth_mode.as_deref())?; Self::push_metadata_tag( &mut tags, diff --git a/codex-rs/otel/src/metrics/client.rs b/codex-rs/otel/src/metrics/client.rs index 386f811422..417c1f4bd9 100644 --- a/codex-rs/otel/src/metrics/client.rs +++ b/codex-rs/otel/src/metrics/client.rs @@ -9,6 +9,7 @@ use crate::metrics::validation::validate_metric_name; use crate::metrics::validation::validate_tag_key; use crate::metrics::validation::validate_tag_value; use crate::metrics::validation::validate_tags; +use codex_utils_string::sanitize_metric_tag_value; use opentelemetry::KeyValue; use opentelemetry::metrics::Counter; use opentelemetry::metrics::Histogram; @@ -197,12 +198,17 @@ impl MetricsClient { validate_tags(&default_tags)?; + let mut resource_attributes = Vec::with_capacity(4); + resource_attributes.push(KeyValue::new( + semconv::attribute::SERVICE_VERSION, + service_version, + )); + resource_attributes.push(KeyValue::new(ENV_ATTRIBUTE, environment)); + resource_attributes.extend(os_resource_attributes()); + let resource = Resource::builder() .with_service_name(service_name) - .with_attributes(vec![ - KeyValue::new(semconv::attribute::SERVICE_VERSION, service_version), - KeyValue::new(ENV_ATTRIBUTE, environment), - ]) + .with_attributes(resource_attributes) .build(); let runtime_reader = runtime_reader.then(|| { @@ -284,6 +290,22 @@ impl MetricsClient { } } +fn os_resource_attributes() -> Vec { + let os_info = os_info::get(); + let os_type_raw = os_info.os_type().to_string(); + let os_type = sanitize_metric_tag_value(os_type_raw.as_str()); + let os_version_raw = os_info.version().to_string(); + let os_version = sanitize_metric_tag_value(os_version_raw.as_str()); + let mut attributes = Vec::new(); + if os_type != "unspecified" { + attributes.push(KeyValue::new("os", os_type)); + } + if os_version != "unspecified" { + attributes.push(KeyValue::new("os_version", os_version)); + } + attributes +} + fn build_provider( resource: Resource, exporter: E, diff --git a/codex-rs/utils/string/Cargo.toml b/codex-rs/utils/string/Cargo.toml index 29437eaefc..8a62539382 100644 --- a/codex-rs/utils/string/Cargo.toml +++ b/codex-rs/utils/string/Cargo.toml @@ -6,3 +6,6 @@ license.workspace = true [lints] workspace = true + +[dev-dependencies] +pretty_assertions = { workspace = true } diff --git a/codex-rs/utils/string/src/lib.rs b/codex-rs/utils/string/src/lib.rs index f7299d4372..b69f185649 100644 --- a/codex-rs/utils/string/src/lib.rs +++ b/codex-rs/utils/string/src/lib.rs @@ -36,3 +36,46 @@ pub fn take_last_bytes_at_char_boundary(s: &str, maxb: usize) -> &str { } &s[start..] } + +/// Sanitize a tag value to comply with metric tag validation rules: +/// only ASCII alphanumeric, '.', '_', '-', and '/' are allowed. +pub fn sanitize_metric_tag_value(value: &str) -> String { + const MAX_LEN: usize = 256; + let sanitized: String = value + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-' | '/') { + ch + } else { + '_' + } + }) + .collect(); + let trimmed = sanitized.trim_matches('_'); + if trimmed.is_empty() || trimmed.chars().all(|ch| !ch.is_ascii_alphanumeric()) { + return "unspecified".to_string(); + } + if trimmed.len() <= MAX_LEN { + trimmed.to_string() + } else { + trimmed[..MAX_LEN].to_string() + } +} + +#[cfg(test)] +mod tests { + use super::sanitize_metric_tag_value; + use pretty_assertions::assert_eq; + + #[test] + fn sanitize_metric_tag_value_trims_and_fills_unspecified() { + let msg = "///"; + assert_eq!(sanitize_metric_tag_value(msg), "unspecified"); + } + + #[test] + fn sanitize_metric_tag_value_replaces_invalid_chars() { + let msg = "bad value!"; + assert_eq!(sanitize_metric_tag_value(msg), "bad_value"); + } +} diff --git a/codex-rs/windows-sandbox-rs/src/lib.rs b/codex-rs/windows-sandbox-rs/src/lib.rs index b938a86e6b..4a97efaaf8 100644 --- a/codex-rs/windows-sandbox-rs/src/lib.rs +++ b/codex-rs/windows-sandbox-rs/src/lib.rs @@ -91,7 +91,7 @@ pub use setup::SETUP_VERSION; #[cfg(target_os = "windows")] pub use setup_error::extract_failure as extract_setup_failure; #[cfg(target_os = "windows")] -pub use setup_error::sanitize_tag_value as sanitize_setup_metric_tag_value; +pub use setup_error::sanitize_setup_metric_tag_value; #[cfg(target_os = "windows")] pub use setup_error::setup_error_path; #[cfg(target_os = "windows")] diff --git a/codex-rs/windows-sandbox-rs/src/setup_error.rs b/codex-rs/windows-sandbox-rs/src/setup_error.rs index 9ee9ebf7f5..23022450a0 100644 --- a/codex-rs/windows-sandbox-rs/src/setup_error.rs +++ b/codex-rs/windows-sandbox-rs/src/setup_error.rs @@ -1,5 +1,6 @@ use anyhow::Context; use anyhow::Result; +use codex_utils_string::sanitize_metric_tag_value; use serde::Deserialize; use serde::Serialize; use std::fs; @@ -126,7 +127,7 @@ impl SetupFailure { } pub fn metric_message(&self) -> String { - sanitize_tag_value(&self.message) + sanitize_setup_metric_tag_value(&self.message) } } @@ -181,30 +182,9 @@ pub fn read_setup_error_report(codex_home: &Path) -> Result String { - const MAX_LEN: usize = 256; - let redacted = redact_home_paths(value); - let sanitized: String = redacted - .chars() - .map(|ch| { - if ch.is_ascii_alphanumeric() || matches!(ch, '.' | '_' | '-' | '/') { - ch - } else { - '_' - } - }) - .collect(); - let trimmed = sanitized.trim_matches('_'); - if trimmed.is_empty() { - return "unspecified".to_string(); - } - if trimmed.len() <= MAX_LEN { - trimmed.to_string() - } else { - trimmed[..MAX_LEN].to_string() - } +/// Sanitize a setup error message for use as a metric tag. +pub fn sanitize_setup_metric_tag_value(value: &str) -> String { + sanitize_metric_tag_value(redact_home_paths(value).as_str()) } fn redact_home_paths(value: &str) -> String { @@ -267,7 +247,7 @@ fn redact_username_segments(value: &str, usernames: &[String]) -> String { #[cfg(test)] mod tests { - use super::*; + use super::redact_username_segments; use pretty_assertions::assert_eq; #[test] From 4033f905c6e72ed601d3370bfef5a70fe74fa170 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 14:34:34 +0000 Subject: [PATCH 59/90] feat: resumable backfill (#10745) ## Summary This PR makes SQLite rollout backfill resumable and repeatable instead of one-shot-on-db-create. ## What changed - Added a persisted backfill state table: - state/migrations/0008_backfill_state.sql - Tracks status (pending|running|complete), last_watermark, and last_success_at. - Added backfill state model/types in codex-state: - BackfillState, BackfillStatus (state/src/model/backfill_state.rs) - Added runtime APIs to manage backfill lifecycle/progress: - get_backfill_state - mark_backfill_running - checkpoint_backfill - mark_backfill_complete - Updated core startup behavior: - Backfill now runs whenever state is not Complete (not only when DB file is newly created). - Reworked backfill execution: - Collect rollout files, derive deterministic watermark per path, sort, resume from last_watermark. - Process in batches (BACKFILL_BATCH_SIZE = 200), checkpoint after each batch. - Mark complete with last_success_at at the end. ## Why Previous behavior could leave users permanently partially backfilled if the process exited during initial async backfill. This change allows safe continuation across restarts and avoids restarting from scratch. --- codex-rs/core/src/rollout/metadata.rs | 330 ++++++++++++++---- codex-rs/core/src/rollout/recorder.rs | 5 +- codex-rs/core/src/state_db.rs | 14 +- .../state/migrations/0008_backfill_state.sql | 17 + codex-rs/state/src/lib.rs | 6 +- codex-rs/state/src/model/backfill_state.rs | 73 ++++ codex-rs/state/src/model/mod.rs | 3 + codex-rs/state/src/runtime.rs | 148 +++++++- 8 files changed, 528 insertions(+), 68 deletions(-) create mode 100644 codex-rs/state/migrations/0008_backfill_state.sql create mode 100644 codex-rs/state/src/model/backfill_state.rs diff --git a/codex-rs/core/src/rollout/metadata.rs b/codex-rs/core/src/rollout/metadata.rs index ec11ca3249..b87879f4c7 100644 --- a/codex-rs/core/src/rollout/metadata.rs +++ b/codex-rs/core/src/rollout/metadata.rs @@ -13,14 +13,15 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; +use codex_state::BackfillState; use codex_state::BackfillStats; +use codex_state::BackfillStatus; use codex_state::DB_ERROR_METRIC; use codex_state::DB_METRIC_BACKFILL; use codex_state::DB_METRIC_BACKFILL_DURATION_MS; use codex_state::ExtractionOutcome; use codex_state::ThreadMetadataBuilder; use codex_state::apply_rollout_item; -use std::cmp::Reverse; use std::path::Path; use std::path::PathBuf; use tracing::info; @@ -28,6 +29,7 @@ use tracing::warn; const ROLLOUT_PREFIX: &str = "rollout-"; const ROLLOUT_SUFFIX: &str = ".jsonl"; +const BACKFILL_BATCH_SIZE: usize = 200; pub(crate) fn builder_from_session_meta( session_meta: &SessionMetaLine, @@ -130,16 +132,52 @@ pub(crate) async fn backfill_sessions( otel: Option<&OtelManager>, ) { let timer = otel.and_then(|otel| otel.start_timer(DB_METRIC_BACKFILL_DURATION_MS, &[]).ok()); + let mut backfill_state = match runtime.get_backfill_state().await { + Ok(state) => state, + Err(err) => { + warn!( + "failed to read backfill state at {}: {err}", + config.codex_home.display() + ); + if let Some(otel) = otel { + otel.counter(DB_ERROR_METRIC, 1, &[("stage", "backfill_state_read")]); + } + BackfillState::default() + } + }; + if backfill_state.status == BackfillStatus::Complete { + return; + } + if let Err(err) = runtime.mark_backfill_running().await { + warn!( + "failed to mark backfill running at {}: {err}", + config.codex_home.display() + ); + if let Some(otel) = otel { + otel.counter( + DB_ERROR_METRIC, + 1, + &[("stage", "backfill_state_mark_running")], + ); + } + } else { + backfill_state.status = BackfillStatus::Running; + } + let sessions_root = config.codex_home.join(rollout::SESSIONS_SUBDIR); let archived_root = config.codex_home.join(rollout::ARCHIVED_SESSIONS_SUBDIR); - let mut rollout_paths: Vec<(PathBuf, bool)> = Vec::new(); + let mut rollout_paths: Vec = Vec::new(); for (root, archived) in [(sessions_root, false), (archived_root, true)] { if !tokio::fs::try_exists(&root).await.unwrap_or(false) { continue; } match collect_rollout_paths(&root).await { Ok(paths) => { - rollout_paths.extend(paths.into_iter().map(|path| (path, archived))); + rollout_paths.extend(paths.into_iter().map(|path| BackfillRolloutPath { + watermark: backfill_watermark_for_path(config.codex_home.as_path(), &path), + path, + archived, + })); } Err(err) => { warn!( @@ -149,75 +187,126 @@ pub(crate) async fn backfill_sessions( } } } - rollout_paths.sort_by_key(|(path, _archived)| { - let parsed = path - .file_name() - .and_then(|name| name.to_str()) - .and_then(parse_timestamp_uuid_from_filename) - .unwrap_or((time::OffsetDateTime::UNIX_EPOCH, uuid::Uuid::nil())); - (Reverse(parsed.0), Reverse(parsed.1)) - }); + rollout_paths.sort_by(|a, b| a.watermark.cmp(&b.watermark)); + if let Some(last_watermark) = backfill_state.last_watermark.as_deref() { + rollout_paths.retain(|entry| entry.watermark.as_str() > last_watermark); + } + let mut stats = BackfillStats { scanned: 0, upserted: 0, failed: 0, }; - for (path, archived) in rollout_paths { - stats.scanned = stats.scanned.saturating_add(1); - match extract_metadata_from_rollout(&path, config.model_provider_id.as_str(), otel).await { - Ok(outcome) => { - if outcome.parse_errors > 0 - && let Some(otel) = otel - { - otel.counter( - DB_ERROR_METRIC, - outcome.parse_errors as i64, - &[("stage", "backfill_sessions")], - ); - } - let mut metadata = outcome.metadata; - if archived && metadata.archived_at.is_none() { - let fallback_archived_at = metadata.updated_at; - metadata.archived_at = file_modified_time_utc(&path) - .await - .or(Some(fallback_archived_at)); - } - if let Err(err) = runtime.upsert_thread(&metadata).await { - stats.failed = stats.failed.saturating_add(1); - warn!("failed to upsert rollout {}: {err}", path.display()); - } else { - stats.upserted = stats.upserted.saturating_add(1); - if let Ok(meta_line) = rollout::list::read_session_meta_line(&path).await { - if let Err(err) = runtime - .persist_dynamic_tools( - meta_line.meta.id, - meta_line.meta.dynamic_tools.as_deref(), - ) - .await - { - if let Some(otel) = otel { - otel.counter( - DB_ERROR_METRIC, - 1, - &[("stage", "backfill_dynamic_tools")], - ); - } - warn!("failed to backfill dynamic tools {}: {err}", path.display()); - } - } else { - warn!( - "failed to read session meta for dynamic tools {}", - path.display() + let mut last_watermark = backfill_state.last_watermark.clone(); + for batch in rollout_paths.chunks(BACKFILL_BATCH_SIZE) { + for rollout in batch { + stats.scanned = stats.scanned.saturating_add(1); + match extract_metadata_from_rollout( + &rollout.path, + config.model_provider_id.as_str(), + otel, + ) + .await + { + Ok(outcome) => { + if outcome.parse_errors > 0 + && let Some(otel) = otel + { + otel.counter( + DB_ERROR_METRIC, + outcome.parse_errors as i64, + &[("stage", "backfill_sessions")], ); } + let mut metadata = outcome.metadata; + if rollout.archived && metadata.archived_at.is_none() { + let fallback_archived_at = metadata.updated_at; + metadata.archived_at = file_modified_time_utc(&rollout.path) + .await + .or(Some(fallback_archived_at)); + } + if let Err(err) = runtime.upsert_thread(&metadata).await { + stats.failed = stats.failed.saturating_add(1); + warn!("failed to upsert rollout {}: {err}", rollout.path.display()); + } else { + stats.upserted = stats.upserted.saturating_add(1); + if let Ok(meta_line) = + rollout::list::read_session_meta_line(&rollout.path).await + { + if let Err(err) = runtime + .persist_dynamic_tools( + meta_line.meta.id, + meta_line.meta.dynamic_tools.as_deref(), + ) + .await + { + if let Some(otel) = otel { + otel.counter( + DB_ERROR_METRIC, + 1, + &[("stage", "backfill_dynamic_tools")], + ); + } + warn!( + "failed to backfill dynamic tools {}: {err}", + rollout.path.display() + ); + } + } else { + warn!( + "failed to read session meta for dynamic tools {}", + rollout.path.display() + ); + } + } + } + Err(err) => { + stats.failed = stats.failed.saturating_add(1); + warn!( + "failed to extract rollout {}: {err}", + rollout.path.display() + ); } } - Err(err) => { - stats.failed = stats.failed.saturating_add(1); - warn!("failed to extract rollout {}: {err}", path.display()); + } + + if let Some(last_entry) = batch.last() { + if let Err(err) = runtime + .checkpoint_backfill(last_entry.watermark.as_str()) + .await + { + warn!( + "failed to checkpoint backfill at {}: {err}", + config.codex_home.display() + ); + if let Some(otel) = otel { + otel.counter( + DB_ERROR_METRIC, + 1, + &[("stage", "backfill_state_checkpoint")], + ); + } + } else { + last_watermark = Some(last_entry.watermark.clone()); } } } + if let Err(err) = runtime + .mark_backfill_complete(last_watermark.as_deref()) + .await + { + warn!( + "failed to mark backfill complete at {}: {err}", + config.codex_home.display() + ); + if let Some(otel) = otel { + otel.counter( + DB_ERROR_METRIC, + 1, + &[("stage", "backfill_state_mark_complete")], + ); + } + } info!( "state db backfill scanned={}, upserted={}, failed={}", @@ -247,6 +336,20 @@ pub(crate) async fn backfill_sessions( } } +#[derive(Debug, Clone)] +struct BackfillRolloutPath { + watermark: String, + path: PathBuf, + archived: bool, +} + +fn backfill_watermark_for_path(codex_home: &Path, path: &Path) -> String { + path.strip_prefix(codex_home) + .unwrap_or(path) + .to_string_lossy() + .replace('\\', "/") +} + async fn file_modified_time_utc(path: &Path) -> Option> { let modified = tokio::fs::metadata(path).await.ok()?.modified().ok()?; let updated_at: DateTime = modified.into(); @@ -331,10 +434,13 @@ mod tests { use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; + use codex_state::BackfillStatus; use codex_state::ThreadMetadataBuilder; use pretty_assertions::assert_eq; use std::fs::File; use std::io::Write; + use std::path::Path; + use std::path::PathBuf; use tempfile::tempdir; use uuid::Uuid; @@ -412,4 +518,108 @@ mod tests { assert_eq!(builder, expected); } + + #[tokio::test] + async fn backfill_sessions_resumes_from_watermark_and_marks_complete() { + let dir = tempdir().expect("tempdir"); + let codex_home = dir.path().to_path_buf(); + let first_uuid = Uuid::new_v4(); + let second_uuid = Uuid::new_v4(); + let first_path = write_rollout_in_sessions( + codex_home.as_path(), + "2026-01-27T12-34-56", + "2026-01-27T12:34:56Z", + first_uuid, + ); + let second_path = write_rollout_in_sessions( + codex_home.as_path(), + "2026-01-27T12-35-56", + "2026-01-27T12:35:56Z", + second_uuid, + ); + + let runtime = + codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + let first_watermark = + backfill_watermark_for_path(codex_home.as_path(), first_path.as_path()); + runtime.mark_backfill_running().await.expect("mark running"); + runtime + .checkpoint_backfill(first_watermark.as_str()) + .await + .expect("checkpoint first watermark"); + + let mut config = crate::config::test_config(); + config.codex_home = codex_home.clone(); + config.model_provider_id = "test-provider".to_string(); + backfill_sessions(runtime.as_ref(), &config, None).await; + + let first_id = ThreadId::from_string(&first_uuid.to_string()).expect("first thread id"); + let second_id = ThreadId::from_string(&second_uuid.to_string()).expect("second thread id"); + assert_eq!( + runtime + .get_thread(first_id) + .await + .expect("get first thread"), + None + ); + assert!( + runtime + .get_thread(second_id) + .await + .expect("get second thread") + .is_some() + ); + + let state = runtime + .get_backfill_state() + .await + .expect("get backfill state"); + assert_eq!(state.status, BackfillStatus::Complete); + assert_eq!( + state.last_watermark, + Some(backfill_watermark_for_path( + codex_home.as_path(), + second_path.as_path() + )) + ); + assert!(state.last_success_at.is_some()); + } + + fn write_rollout_in_sessions( + codex_home: &Path, + filename_ts: &str, + event_ts: &str, + thread_uuid: Uuid, + ) -> PathBuf { + let id = ThreadId::from_string(&thread_uuid.to_string()).expect("thread id"); + let sessions_dir = codex_home.join("sessions"); + std::fs::create_dir_all(sessions_dir.as_path()).expect("create sessions dir"); + let path = sessions_dir.join(format!("rollout-{filename_ts}-{thread_uuid}.jsonl")); + let session_meta = SessionMeta { + id, + forked_from_id: None, + timestamp: event_ts.to_string(), + cwd: codex_home.to_path_buf(), + originator: "cli".to_string(), + cli_version: "0.0.0".to_string(), + source: SessionSource::default(), + model_provider: Some("test-provider".to_string()), + base_instructions: None, + dynamic_tools: None, + }; + let session_meta_line = SessionMetaLine { + meta: session_meta, + git: None, + }; + let rollout_line = RolloutLine { + timestamp: event_ts.to_string(), + item: RolloutItem::SessionMeta(session_meta_line), + }; + let json = serde_json::to_string(&rollout_line).expect("serialize rollout"); + let mut file = File::create(&path).expect("create rollout"); + writeln!(file, "{json}").expect("write rollout"); + path + } } diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 465bcbbcdf..89f8829a23 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -19,6 +19,7 @@ use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::{self}; use tokio::sync::oneshot; use tracing::info; +use tracing::trace; use tracing::warn; use super::ARCHIVED_SESSIONS_SUBDIR; @@ -386,7 +387,7 @@ impl RolloutRecorder { pub(crate) async fn load_rollout_items( path: &Path, ) -> std::io::Result<(Vec, Option, usize)> { - info!("Resuming rollout from {path:?}"); + trace!("Resuming rollout from {path:?}"); let text = tokio::fs::read_to_string(path).await?; if text.trim().is_empty() { return Err(IoError::other("empty session file")); @@ -433,7 +434,7 @@ impl RolloutRecorder { } }, Err(e) => { - warn!("failed to parse rollout line: {e}"); + trace!("failed to parse rollout line: {e}"); parse_errors = parse_errors.saturating_add(1); } } diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index c36d8afe4b..4ea1435ead 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -31,11 +31,9 @@ pub(crate) async fn init_if_enabled( config: &Config, otel: Option<&OtelManager>, ) -> Option { - let state_path = codex_state::state_db_path(config.codex_home.as_path()); if !config.features.enabled(Feature::Sqlite) { return None; } - let existed = tokio::fs::try_exists(&state_path).await.unwrap_or(false); let runtime = match codex_state::StateRuntime::init( config.codex_home.clone(), config.model_provider_id.clone(), @@ -55,7 +53,17 @@ pub(crate) async fn init_if_enabled( return None; } }; - if !existed { + let should_backfill = match runtime.get_backfill_state().await { + Ok(state) => state.status != codex_state::BackfillStatus::Complete, + Err(err) => { + warn!( + "failed to read backfill state at {}: {err}", + config.codex_home.display() + ); + true + } + }; + if should_backfill { let runtime_for_backfill = Arc::clone(&runtime); let config_for_backfill = config.clone(); let otel_for_backfill = otel.cloned(); diff --git a/codex-rs/state/migrations/0008_backfill_state.sql b/codex-rs/state/migrations/0008_backfill_state.sql new file mode 100644 index 0000000000..c9fc1fdeb7 --- /dev/null +++ b/codex-rs/state/migrations/0008_backfill_state.sql @@ -0,0 +1,17 @@ +CREATE TABLE backfill_state ( + id INTEGER PRIMARY KEY CHECK (id = 1), + status TEXT NOT NULL, + last_watermark TEXT, + last_success_at INTEGER, + updated_at INTEGER NOT NULL +); + +INSERT INTO backfill_state (id, status, last_watermark, last_success_at, updated_at) +VALUES ( + 1, + 'pending', + NULL, + NULL, + CAST(strftime('%s', 'now') AS INTEGER) +) +ON CONFLICT(id) DO NOTHING; diff --git a/codex-rs/state/src/lib.rs b/codex-rs/state/src/lib.rs index 7df3374096..1625554e29 100644 --- a/codex-rs/state/src/lib.rs +++ b/codex-rs/state/src/lib.rs @@ -22,7 +22,9 @@ pub use runtime::StateRuntime; /// Most consumers should prefer [`StateRuntime`]. pub use extract::apply_rollout_item; pub use model::Anchor; +pub use model::BackfillState; pub use model::BackfillStats; +pub use model::BackfillStatus; pub use model::ExtractionOutcome; pub use model::SortKey; pub use model::ThreadMemory; @@ -36,9 +38,9 @@ pub use runtime::state_db_path; /// Errors encountered during DB operations. Tags: [stage] pub const DB_ERROR_METRIC: &str = "codex.db.error"; -/// Metrics on backfill process during first init of the db. Tags: [status] +/// Metrics on backfill process. Tags: [status] pub const DB_METRIC_BACKFILL: &str = "codex.db.backfill"; -/// Metrics on backfill duration during first init of the db. Tags: [status] +/// Metrics on backfill duration. Tags: [status] pub const DB_METRIC_BACKFILL_DURATION_MS: &str = "codex.db.backfill.duration_ms"; /// Metrics on errors during comparison between DB and rollout file. Tags: [stage] pub const DB_METRIC_COMPARE_ERROR: &str = "codex.db.compare_error"; diff --git a/codex-rs/state/src/model/backfill_state.rs b/codex-rs/state/src/model/backfill_state.rs new file mode 100644 index 0000000000..353929f980 --- /dev/null +++ b/codex-rs/state/src/model/backfill_state.rs @@ -0,0 +1,73 @@ +use anyhow::Result; +use chrono::DateTime; +use chrono::Utc; +use sqlx::Row; +use sqlx::sqlite::SqliteRow; + +/// Persisted lifecycle state for rollout metadata backfill. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BackfillState { + /// Current lifecycle status. + pub status: BackfillStatus, + /// Last processed rollout watermark. + pub last_watermark: Option, + /// Last successful completion time. + pub last_success_at: Option>, +} + +impl Default for BackfillState { + fn default() -> Self { + Self { + status: BackfillStatus::Pending, + last_watermark: None, + last_success_at: None, + } + } +} + +impl BackfillState { + pub(crate) fn try_from_row(row: &SqliteRow) -> Result { + let status: String = row.try_get("status")?; + let last_success_at = row + .try_get::, _>("last_success_at")? + .map(epoch_seconds_to_datetime) + .transpose()?; + Ok(Self { + status: BackfillStatus::parse(status.as_str())?, + last_watermark: row.try_get("last_watermark")?, + last_success_at, + }) + } +} + +/// Backfill lifecycle status. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BackfillStatus { + Pending, + Running, + Complete, +} + +impl BackfillStatus { + pub const fn as_str(self) -> &'static str { + match self { + BackfillStatus::Pending => "pending", + BackfillStatus::Running => "running", + BackfillStatus::Complete => "complete", + } + } + + pub fn parse(value: &str) -> Result { + match value { + "pending" => Ok(Self::Pending), + "running" => Ok(Self::Running), + "complete" => Ok(Self::Complete), + _ => Err(anyhow::anyhow!("invalid backfill status: {value}")), + } + } +} + +fn epoch_seconds_to_datetime(secs: i64) -> Result> { + DateTime::::from_timestamp(secs, 0) + .ok_or_else(|| anyhow::anyhow!("invalid unix timestamp: {secs}")) +} diff --git a/codex-rs/state/src/model/mod.rs b/codex-rs/state/src/model/mod.rs index d937019ec1..6bec8875dc 100644 --- a/codex-rs/state/src/model/mod.rs +++ b/codex-rs/state/src/model/mod.rs @@ -1,7 +1,10 @@ +mod backfill_state; mod log; mod thread_memory; mod thread_metadata; +pub use backfill_state::BackfillState; +pub use backfill_state::BackfillStatus; pub use log::LogEntry; pub use log::LogQuery; pub use log::LogRow; diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 25c666fa6e..ae28a02197 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -91,6 +91,80 @@ impl StateRuntime { self.codex_home.as_path() } + /// Get persisted rollout metadata backfill state. + pub async fn get_backfill_state(&self) -> anyhow::Result { + self.ensure_backfill_state_row().await?; + let row = sqlx::query( + r#" +SELECT status, last_watermark, last_success_at +FROM backfill_state +WHERE id = 1 + "#, + ) + .fetch_one(self.pool.as_ref()) + .await?; + crate::BackfillState::try_from_row(&row) + } + + /// Mark rollout metadata backfill as running. + pub async fn mark_backfill_running(&self) -> anyhow::Result<()> { + self.ensure_backfill_state_row().await?; + sqlx::query( + r#" +UPDATE backfill_state +SET status = ?, updated_at = ? +WHERE id = 1 + "#, + ) + .bind(crate::BackfillStatus::Running.as_str()) + .bind(Utc::now().timestamp()) + .execute(self.pool.as_ref()) + .await?; + Ok(()) + } + + /// Persist rollout metadata backfill progress. + pub async fn checkpoint_backfill(&self, watermark: &str) -> anyhow::Result<()> { + self.ensure_backfill_state_row().await?; + sqlx::query( + r#" +UPDATE backfill_state +SET status = ?, last_watermark = ?, updated_at = ? +WHERE id = 1 + "#, + ) + .bind(crate::BackfillStatus::Running.as_str()) + .bind(watermark) + .bind(Utc::now().timestamp()) + .execute(self.pool.as_ref()) + .await?; + Ok(()) + } + + /// Mark rollout metadata backfill as complete. + pub async fn mark_backfill_complete(&self, last_watermark: Option<&str>) -> anyhow::Result<()> { + self.ensure_backfill_state_row().await?; + let now = Utc::now().timestamp(); + sqlx::query( + r#" +UPDATE backfill_state +SET + status = ?, + last_watermark = COALESCE(?, last_watermark), + last_success_at = ?, + updated_at = ? +WHERE id = 1 + "#, + ) + .bind(crate::BackfillStatus::Complete.as_str()) + .bind(last_watermark) + .bind(now) + .bind(now) + .execute(self.pool.as_ref()) + .await?; + Ok(()) + } + /// Load thread metadata by id using the underlying database. pub async fn get_thread(&self, id: ThreadId) -> anyhow::Result> { let row = sqlx::query( @@ -637,6 +711,22 @@ ON CONFLICT(thread_id, position) DO NOTHING } self.upsert_thread(&metadata).await } + + async fn ensure_backfill_state_row(&self) -> anyhow::Result<()> { + sqlx::query( + r#" +INSERT INTO backfill_state (id, status, last_watermark, last_success_at, updated_at) +VALUES (?, ?, NULL, NULL, ?) +ON CONFLICT(id) DO NOTHING + "#, + ) + .bind(1_i64) + .bind(crate::BackfillStatus::Pending.as_str()) + .bind(Utc::now().timestamp()) + .execute(self.pool.as_ref()) + .await?; + Ok(()) + } } fn push_log_filters<'a>(builder: &mut QueryBuilder<'a, Sqlite>, query: &'a LogQuery) { @@ -889,7 +979,10 @@ mod tests { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .map_or(0, |duration| duration.as_nanos()); - std::env::temp_dir().join(format!("codex-state-runtime-test-{nanos}")) + std::env::temp_dir().join(format!( + "codex-state-runtime-test-{nanos}-{}", + Uuid::new_v4() + )) } #[tokio::test] @@ -967,6 +1060,59 @@ mod tests { let _ = tokio::fs::remove_dir_all(codex_home).await; } + #[tokio::test] + async fn backfill_state_persists_progress_and_completion() { + let codex_home = unique_temp_dir(); + let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string(), None) + .await + .expect("initialize runtime"); + + let initial = runtime + .get_backfill_state() + .await + .expect("get initial backfill state"); + assert_eq!(initial.status, crate::BackfillStatus::Pending); + assert_eq!(initial.last_watermark, None); + assert_eq!(initial.last_success_at, None); + + runtime + .mark_backfill_running() + .await + .expect("mark backfill running"); + runtime + .checkpoint_backfill("sessions/2026/01/27/rollout-a.jsonl") + .await + .expect("checkpoint backfill"); + + let running = runtime + .get_backfill_state() + .await + .expect("get running backfill state"); + assert_eq!(running.status, crate::BackfillStatus::Running); + assert_eq!( + running.last_watermark, + Some("sessions/2026/01/27/rollout-a.jsonl".to_string()) + ); + assert_eq!(running.last_success_at, None); + + runtime + .mark_backfill_complete(Some("sessions/2026/01/28/rollout-b.jsonl")) + .await + .expect("mark backfill complete"); + let completed = runtime + .get_backfill_state() + .await + .expect("get completed backfill state"); + assert_eq!(completed.status, crate::BackfillStatus::Complete); + assert_eq!( + completed.last_watermark, + Some("sessions/2026/01/28/rollout-b.jsonl".to_string()) + ); + assert!(completed.last_success_at.is_some()); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + #[tokio::test] async fn upsert_and_get_thread_memory() { let codex_home = unique_temp_dir(); From d337b51741bfb6b0caa452146535582e7a5cddda Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 15:49:57 +0000 Subject: [PATCH 60/90] feat: wire ephemeral in `codex exec` (#10758) --- codex-rs/README.md | 1 + codex-rs/exec/src/cli.rs | 6 +++ codex-rs/exec/src/lib.rs | 3 +- codex-rs/exec/tests/suite/ephemeral.rs | 55 ++++++++++++++++++++++++++ codex-rs/exec/tests/suite/mod.rs | 1 + 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 codex-rs/exec/tests/suite/ephemeral.rs diff --git a/codex-rs/README.md b/codex-rs/README.md index cbe1fe3779..9cdd772b2a 100644 --- a/codex-rs/README.md +++ b/codex-rs/README.md @@ -51,6 +51,7 @@ You can enable notifications by configuring a script that is run whenever the ag ### `codex exec` to run Codex programmatically/non-interactively To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on. +Use `codex exec --ephemeral ...` to run without persisting session rollout files to disk. ### Experimenting with the Codex Sandbox diff --git a/codex-rs/exec/src/cli.rs b/codex-rs/exec/src/cli.rs index c27c39f010..5d7a9ba725 100644 --- a/codex-rs/exec/src/cli.rs +++ b/codex-rs/exec/src/cli.rs @@ -71,6 +71,10 @@ pub struct Cli { #[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)] pub add_dir: Vec, + /// Run without persisting session files to disk. + #[arg(long = "ephemeral", global = true, default_value_t = false)] + pub ephemeral: bool, + /// Path to a JSON Schema file describing the model's final response shape. #[arg(long = "output-schema", value_name = "FILE")] pub output_schema: Option, @@ -262,9 +266,11 @@ mod tests { "gpt-5.2-codex", "--dangerously-bypass-approvals-and-sandbox", "--skip-git-repo-check", + "--ephemeral", PROMPT, ]); + assert!(cli.ephemeral); let Some(Command::Resume(args)) = cli.command else { panic!("expected resume command"); }; diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index a6ded3927a..0f8e8ad72e 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -103,6 +103,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any cwd, skip_git_repo_check, add_dir, + ephemeral, color, last_message_file, json: json_mode, @@ -254,7 +255,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any include_apply_patch_tool: None, show_raw_agent_reasoning: oss.then_some(true), tools_web_search_request: None, - ephemeral: None, + ephemeral: ephemeral.then_some(true), additional_writable_roots: add_dir, }; diff --git a/codex-rs/exec/tests/suite/ephemeral.rs b/codex-rs/exec/tests/suite/ephemeral.rs new file mode 100644 index 0000000000..533a732f94 --- /dev/null +++ b/codex-rs/exec/tests/suite/ephemeral.rs @@ -0,0 +1,55 @@ +#![cfg(not(target_os = "windows"))] +#![allow(clippy::expect_used, clippy::unwrap_used)] + +use codex_utils_cargo_bin::find_resource; +use core_test_support::test_codex_exec::test_codex_exec; +use walkdir::WalkDir; + +fn session_rollout_count(home_path: &std::path::Path) -> usize { + let sessions_dir = home_path.join("sessions"); + if !sessions_dir.exists() { + return 0; + } + + WalkDir::new(sessions_dir) + .into_iter() + .filter_map(Result::ok) + .filter(|entry| entry.file_type().is_file()) + .filter(|entry| entry.file_name().to_string_lossy().ends_with(".jsonl")) + .count() +} + +#[test] +fn persists_rollout_file_by_default() -> anyhow::Result<()> { + let test = test_codex_exec(); + let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?; + + test.cmd() + .env("CODEX_RS_SSE_FIXTURE", &fixture) + .env("OPENAI_BASE_URL", "http://unused.local") + .arg("--skip-git-repo-check") + .arg("default persistence behavior") + .assert() + .code(0); + + assert_eq!(session_rollout_count(test.home_path()), 1); + Ok(()) +} + +#[test] +fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()> { + let test = test_codex_exec(); + let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?; + + test.cmd() + .env("CODEX_RS_SSE_FIXTURE", &fixture) + .env("OPENAI_BASE_URL", "http://unused.local") + .arg("--skip-git-repo-check") + .arg("--ephemeral") + .arg("ephemeral behavior") + .assert() + .code(0); + + assert_eq!(session_rollout_count(test.home_path()), 0); + Ok(()) +} diff --git a/codex-rs/exec/tests/suite/mod.rs b/codex-rs/exec/tests/suite/mod.rs index 77012ee3b7..dbfc966df4 100644 --- a/codex-rs/exec/tests/suite/mod.rs +++ b/codex-rs/exec/tests/suite/mod.rs @@ -2,6 +2,7 @@ mod add_dir; mod apply_patch; mod auth_env; +mod ephemeral; mod originator; mod output_schema; mod resume; From fe1cbd0f388bd2c15b8087913921be839ca5f381 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 16:07:50 +0000 Subject: [PATCH 61/90] chore: handle shutdown correctly in tui (#10756) --- codex-rs/tui/src/chatwidget/agent.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codex-rs/tui/src/chatwidget/agent.rs b/codex-rs/tui/src/chatwidget/agent.rs index c902e5c638..d905f884f8 100644 --- a/codex-rs/tui/src/chatwidget/agent.rs +++ b/codex-rs/tui/src/chatwidget/agent.rs @@ -62,7 +62,13 @@ pub(crate) fn spawn_agent( }); while let Ok(event) = thread.next_event().await { + let is_shutdown_complete = matches!(event.msg, EventMsg::ShutdownComplete); app_event_tx_clone.send(AppEvent::CodexEvent(event)); + if is_shutdown_complete { + // ShutdownComplete is terminal for a thread; drop this receiver task so + // the Arc can be released and thread resources can clean up. + break; + } } }); @@ -99,7 +105,13 @@ pub(crate) fn spawn_agent_from_existing( }); while let Ok(event) = thread.next_event().await { + let is_shutdown_complete = matches!(event.msg, EventMsg::ShutdownComplete); app_event_tx_clone.send(AppEvent::CodexEvent(event)); + if is_shutdown_complete { + // ShutdownComplete is terminal for a thread; drop this receiver task so + // the Arc can be released and thread resources can clean up. + break; + } } }); From 41f3b1ba0bc3f800ea0dba3d3dc72515fc07c666 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 16:16:31 +0000 Subject: [PATCH 62/90] feat: add memory tool (#10637) Add a tool for memory to retrieve a full memory based on the memory ID --- codex-rs/core/config.schema.json | 6 ++ codex-rs/core/src/features.rs | 8 ++ .../core/src/tools/handlers/get_memory.rs | 72 ++++++++++++++++ codex-rs/core/src/tools/handlers/mod.rs | 2 + codex-rs/core/src/tools/spec.rs | 59 +++++++++++++ codex-rs/core/tests/suite/memory_tool.rs | 84 +++++++++++++++++++ codex-rs/core/tests/suite/mod.rs | 1 + 7 files changed, 232 insertions(+) create mode 100644 codex-rs/core/src/tools/handlers/get_memory.rs create mode 100644 codex-rs/core/tests/suite/memory_tool.rs diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index bf2439723a..a872d1b913 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -190,6 +190,9 @@ "include_apply_patch_tool": { "type": "boolean" }, + "memory_tool": { + "type": "boolean" + }, "personality": { "type": "boolean" }, @@ -1212,6 +1215,9 @@ "include_apply_patch_tool": { "type": "boolean" }, + "memory_tool": { + "type": "boolean" + }, "personality": { "type": "boolean" }, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index e14373f858..ee091979eb 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -107,6 +107,8 @@ pub enum Feature { RuntimeMetrics, /// Persist rollout metadata to a local SQLite database. Sqlite, + /// Enable the get_memory tool backed by SQLite thread memories. + MemoryTool, /// Append additional AGENTS.md guidance to user instructions. ChildAgentsMd, /// Enforce UTF8 output in Powershell. @@ -449,6 +451,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::UnderDevelopment, default_enabled: false, }, + FeatureSpec { + id: Feature::MemoryTool, + key: "memory_tool", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::ChildAgentsMd, key: "child_agents_md", diff --git a/codex-rs/core/src/tools/handlers/get_memory.rs b/codex-rs/core/src/tools/handlers/get_memory.rs new file mode 100644 index 0000000000..df2929b88a --- /dev/null +++ b/codex-rs/core/src/tools/handlers/get_memory.rs @@ -0,0 +1,72 @@ +use crate::function_tool::FunctionCallError; +use crate::state_db; +use crate::tools::context::ToolInvocation; +use crate::tools::context::ToolOutput; +use crate::tools::context::ToolPayload; +use crate::tools::handlers::parse_arguments; +use crate::tools::registry::ToolHandler; +use crate::tools::registry::ToolKind; +use async_trait::async_trait; +use codex_protocol::ThreadId; +use codex_protocol::models::FunctionCallOutputBody; +use serde::Deserialize; +use serde_json::json; + +pub struct GetMemoryHandler; + +#[derive(Deserialize)] +struct GetMemoryArgs { + memory_id: String, +} + +#[async_trait] +impl ToolHandler for GetMemoryHandler { + fn kind(&self) -> ToolKind { + ToolKind::Function + } + + async fn handle(&self, invocation: ToolInvocation) -> Result { + let ToolInvocation { + session, payload, .. + } = invocation; + + let arguments = match payload { + ToolPayload::Function { arguments } => arguments, + _ => { + return Err(FunctionCallError::RespondToModel( + "get_memory handler received unsupported payload".to_string(), + )); + } + }; + + let args: GetMemoryArgs = parse_arguments(&arguments)?; + let thread_id = ThreadId::from_string(args.memory_id.as_str()).map_err(|err| { + FunctionCallError::RespondToModel(format!("memory_id must be a valid thread id: {err}")) + })?; + + let state_db_ctx = session.state_db(); + let memory = + state_db::get_thread_memory(state_db_ctx.as_deref(), thread_id, "get_memory_tool") + .await + .ok_or_else(|| { + FunctionCallError::RespondToModel(format!( + "memory not found for memory_id={}", + args.memory_id + )) + })?; + + let content = serde_json::to_string_pretty(&json!({ + "memory_id": args.memory_id, + "trace_summary": memory.trace_summary, + "memory_summary": memory.memory_summary, + })) + .map_err(|err| { + FunctionCallError::Fatal(format!("failed to serialize memory payload: {err}")) + })?; + + Ok(ToolOutput::Function { + body: FunctionCallOutputBody::Text(content), + success: Some(true), + }) + } +} diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index dda4760bd7..d8ec887162 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -1,6 +1,7 @@ pub mod apply_patch; pub(crate) mod collab; mod dynamic; +mod get_memory; mod grep_files; mod list_dir; mod mcp; @@ -20,6 +21,7 @@ use crate::function_tool::FunctionCallError; pub use apply_patch::ApplyPatchHandler; pub use collab::CollabHandler; pub use dynamic::DynamicToolHandler; +pub use get_memory::GetMemoryHandler; pub use grep_files::GrepFilesHandler; pub use list_dir::ListDirHandler; pub use mcp::McpHandler; diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index ddac191a9b..26fccf318a 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -31,6 +31,7 @@ pub(crate) struct ToolsConfig { pub web_search_mode: Option, pub collab_tools: bool, pub collaboration_modes_tools: bool, + pub memory_tools: bool, pub request_rule_enabled: bool, pub experimental_supported_tools: Vec, } @@ -51,6 +52,7 @@ impl ToolsConfig { let include_apply_patch_tool = features.enabled(Feature::ApplyPatchFreeform); let include_collab_tools = features.enabled(Feature::Collab); let include_collaboration_modes_tools = features.enabled(Feature::CollaborationModes); + let include_memory_tools = features.enabled(Feature::MemoryTool); let request_rule_enabled = features.enabled(Feature::RequestRule); let shell_type = if !features.enabled(Feature::ShellTool) { @@ -84,6 +86,7 @@ impl ToolsConfig { web_search_mode: *web_search_mode, collab_tools: include_collab_tools, collaboration_modes_tools: include_collaboration_modes_tools, + memory_tools: include_memory_tools, request_rule_enabled, experimental_supported_tools: model_info.experimental_supported_tools.clone(), } @@ -634,6 +637,28 @@ fn create_request_user_input_tool() -> ToolSpec { }) } +fn create_get_memory_tool() -> ToolSpec { + let properties = BTreeMap::from([( + "memory_id".to_string(), + JsonSchema::String { + description: Some( + "Memory ID to fetch. Uses the thread ID as the memory identifier.".to_string(), + ), + }, + )]); + + ToolSpec::Function(ResponsesApiTool { + name: "get_memory".to_string(), + description: "Loads the full stored memory payload for a memory_id.".to_string(), + strict: false, + parameters: JsonSchema::Object { + properties, + required: Some(vec!["memory_id".to_string()]), + additional_properties: Some(false.into()), + }, + }) +} + fn create_close_agent_tool() -> ToolSpec { let mut properties = BTreeMap::new(); properties.insert( @@ -1228,6 +1253,7 @@ pub(crate) fn build_specs( use crate::tools::handlers::ApplyPatchHandler; use crate::tools::handlers::CollabHandler; use crate::tools::handlers::DynamicToolHandler; + use crate::tools::handlers::GetMemoryHandler; use crate::tools::handlers::GrepFilesHandler; use crate::tools::handlers::ListDirHandler; use crate::tools::handlers::McpHandler; @@ -1249,6 +1275,7 @@ pub(crate) fn build_specs( let plan_handler = Arc::new(PlanHandler); let apply_patch_handler = Arc::new(ApplyPatchHandler); let dynamic_tool_handler = Arc::new(DynamicToolHandler); + let get_memory_handler = Arc::new(GetMemoryHandler); let view_image_handler = Arc::new(ViewImageHandler); let mcp_handler = Arc::new(McpHandler); let mcp_resource_handler = Arc::new(McpResourceHandler); @@ -1308,6 +1335,11 @@ pub(crate) fn build_specs( builder.register_handler("request_user_input", request_user_input_handler); } + if config.memory_tools { + builder.push_spec(create_get_memory_tool()); + builder.register_handler("get_memory", get_memory_handler); + } + if let Some(apply_patch_tool_type) = &config.apply_patch_tool_type { match apply_patch_tool_type { ApplyPatchToolType::Freeform => { @@ -1669,6 +1701,33 @@ mod tests { assert_contains_tool_names(&tools, &["request_user_input"]); } + #[test] + fn get_memory_requires_memory_tool_feature() { + let config = test_config(); + let model_info = ModelsManager::construct_model_info_offline("gpt-5-codex", &config); + let mut features = Features::with_defaults(); + features.disable(Feature::MemoryTool); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + features: &features, + web_search_mode: Some(WebSearchMode::Cached), + }); + let (tools, _) = build_specs(&tools_config, None, &[]).build(); + assert!( + !tools.iter().any(|t| t.spec.name() == "get_memory"), + "get_memory should be disabled when memory_tool feature is off" + ); + + features.enable(Feature::MemoryTool); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + features: &features, + web_search_mode: Some(WebSearchMode::Cached), + }); + let (tools, _) = build_specs(&tools_config, None, &[]).build(); + assert_contains_tool_names(&tools, &["get_memory"]); + } + fn assert_model_tools( model_slug: &str, features: &Features, diff --git a/codex-rs/core/tests/suite/memory_tool.rs b/codex-rs/core/tests/suite/memory_tool.rs new file mode 100644 index 0000000000..09d1ee3ced --- /dev/null +++ b/codex-rs/core/tests/suite/memory_tool.rs @@ -0,0 +1,84 @@ +#![allow(clippy::expect_used, clippy::unwrap_used)] + +use anyhow::Result; +use codex_core::features::Feature; +use core_test_support::responses::mount_function_call_agent_response; +use core_test_support::responses::start_mock_server; +use core_test_support::skip_if_no_network; +use core_test_support::test_codex::test_codex; +use pretty_assertions::assert_eq; +use serde_json::Value; +use serde_json::json; +use tokio::time::Duration; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_memory_tool_returns_persisted_thread_memory() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex().with_config(|config| { + config.features.enable(Feature::Sqlite); + config.features.enable(Feature::MemoryTool); + }); + let test = builder.build(&server).await?; + + let db = test.codex.state_db().expect("state db enabled"); + let thread_id = test.session_configured.session_id; + let thread_id_string = thread_id.to_string(); + + let mut thread_exists = false; + // Wait for DB creation. + for _ in 0..100 { + if db.get_thread(thread_id).await?.is_some() { + thread_exists = true; + break; + } + tokio::time::sleep(Duration::from_millis(25)).await; + } + assert!(thread_exists, "thread should exist in state db"); + + let trace_summary = "trace summary from sqlite"; + let memory_summary = "memory summary from sqlite"; + db.upsert_thread_memory(thread_id, trace_summary, memory_summary) + .await?; + + let call_id = "memory-call-1"; + let arguments = json!({ + "memory_id": thread_id_string, + }) + .to_string(); + let mocks = + mount_function_call_agent_response(&server, call_id, &arguments, "get_memory").await; + + test.submit_turn("load the saved memory").await?; + + let initial_request = mocks.function_call.single_request().body_json(); + assert!( + initial_request["tools"] + .as_array() + .expect("tools array") + .iter() + .filter_map(|tool| tool.get("name").and_then(Value::as_str)) + .any(|name| name == "get_memory"), + "get_memory tool should be exposed when memory_tool feature is enabled" + ); + + let completion_request = mocks.completion.single_request(); + let (content_opt, success_opt) = completion_request + .function_call_output_content_and_success(call_id) + .expect("function_call_output should be present"); + let success = success_opt.unwrap_or(true); + assert!(success, "expected successful get_memory tool call output"); + let content = content_opt.expect("function_call_output content should be present"); + let payload: Value = serde_json::from_str(&content)?; + assert_eq!( + payload, + json!({ + "memory_id": thread_id_string, + "trace_summary": trace_summary, + "memory_summary": memory_summary, + }) + ); + + Ok(()) +} diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index b903f4e6ca..379f521682 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -82,6 +82,7 @@ mod list_dir; mod list_models; mod live_cli; mod live_reload; +mod memory_tool; mod model_info_overrides; mod model_overrides; mod model_switching; From 901215e310b62c6a4e34558161741cb26e058fb4 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 16:21:49 +0000 Subject: [PATCH 63/90] feat: repair DB in case of missing lines (#10751) --- codex-rs/core/src/rollout/list.rs | 12 ++++- codex-rs/core/src/rollout/recorder.rs | 1 + codex-rs/core/src/rollout/tests.rs | 42 +++++++++++++++++- codex-rs/core/src/state_db.rs | 63 ++++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 4 deletions(-) diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 2cac898d42..092bb22c65 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -1088,9 +1088,10 @@ async fn find_thread_path_by_id_str_in_subdir( ARCHIVED_SESSIONS_SUBDIR => Some(true), _ => None, }; + let thread_id = ThreadId::from_string(id_str).ok(); let state_db_ctx = state_db::open_if_present(codex_home, "").await; if let Some(state_db_ctx) = state_db_ctx.as_deref() - && let Ok(thread_id) = ThreadId::from_string(id_str) + && let Some(thread_id) = thread_id && let Some(db_path) = state_db::find_rollout_path_by_id( Some(state_db_ctx), thread_id, @@ -1128,9 +1129,16 @@ async fn find_thread_path_by_id_str_in_subdir( .map_err(|e| io::Error::other(format!("file search failed: {e}")))?; let found = results.matches.into_iter().next().map(|m| m.full_path()); - if found.is_some() { + if let Some(found_path) = found.as_ref() { tracing::error!("state db missing rollout path for thread {id_str}"); state_db::record_discrepancy("find_thread_path_by_id_str_in_subdir", "falling_back"); + state_db::read_repair_rollout_path( + state_db_ctx.as_deref(), + thread_id, + archived_only, + found_path.as_path(), + ) + .await; } Ok(found) diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 89f8829a23..0817577b28 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -568,6 +568,7 @@ async fn rollout_writer( default_provider.as_str(), state_builder.as_ref(), std::slice::from_ref(&rollout_item), + None, ) .await; } diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index 89df2897db..c4d93415ba 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -250,7 +250,31 @@ async fn find_thread_path_falls_back_when_db_path_is_stale() { let found = crate::rollout::find_thread_path_by_id_str(home, &uuid.to_string()) .await .expect("lookup should succeed"); - assert_eq!(found, Some(fs_rollout_path)); + assert_eq!(found, Some(fs_rollout_path.clone())); + assert_state_db_rollout_path(home, thread_id, Some(fs_rollout_path.as_path())).await; +} + +#[tokio::test] +async fn find_thread_path_repairs_missing_db_row_after_filesystem_fallback() { + let temp = TempDir::new().unwrap(); + let home = temp.path(); + let uuid = Uuid::from_u128(303); + let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id"); + let ts = "2025-01-03T13-00-00"; + write_session_file(home, ts, uuid, 1, Some(SessionSource::Cli)).unwrap(); + let fs_rollout_path = home.join(format!("sessions/2025/01/03/rollout-{ts}-{uuid}.jsonl")); + + // Create an empty state DB so lookup takes the DB-first path and then falls back to files. + let _runtime = + codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string(), None) + .await + .expect("state db should initialize"); + + let found = crate::rollout::find_thread_path_by_id_str(home, &uuid.to_string()) + .await + .expect("lookup should succeed"); + assert_eq!(found, Some(fs_rollout_path.clone())); + assert_state_db_rollout_path(home, thread_id, Some(fs_rollout_path.as_path())).await; } #[test] @@ -263,6 +287,22 @@ fn rollout_date_parts_extracts_directory_components() { ); } +async fn assert_state_db_rollout_path( + home: &Path, + thread_id: ThreadId, + expected_path: Option<&Path>, +) { + let runtime = + codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string(), None) + .await + .expect("state db should initialize"); + let path = runtime + .find_rollout_path_by_id(thread_id, Some(false)) + .await + .expect("state db lookup should succeed"); + assert_eq!(path.as_deref(), expected_path); +} + fn write_session_file( root: &Path, ts_str: &str, diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index 4ea1435ead..d67a065314 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -349,6 +349,7 @@ pub async fn reconcile_rollout( default_provider: &str, builder: Option<&ThreadMetadataBuilder>, items: &[RolloutItem], + archived_only: Option, ) { let Some(ctx) = context else { return; @@ -376,7 +377,17 @@ pub async fn reconcile_rollout( return; } }; - if let Err(err) = ctx.upsert_thread(&outcome.metadata).await { + let mut metadata = outcome.metadata; + match archived_only { + Some(true) if metadata.archived_at.is_none() => { + metadata.archived_at = Some(metadata.updated_at); + } + Some(false) => { + metadata.archived_at = None; + } + Some(true) | None => {} + } + if let Err(err) = ctx.upsert_thread(&metadata).await { warn!( "state db reconcile_rollout upsert failed {}: {err}", rollout_path.display() @@ -399,6 +410,56 @@ pub async fn reconcile_rollout( } } +/// Repair a thread's rollout path after filesystem fallback succeeds. +pub async fn read_repair_rollout_path( + context: Option<&codex_state::StateRuntime>, + thread_id: Option, + archived_only: Option, + rollout_path: &Path, +) { + let Some(ctx) = context else { + return; + }; + + if let Some(thread_id) = thread_id + && let Ok(Some(mut metadata)) = ctx.get_thread(thread_id).await + { + metadata.rollout_path = rollout_path.to_path_buf(); + match archived_only { + Some(true) if metadata.archived_at.is_none() => { + metadata.archived_at = Some(metadata.updated_at); + } + Some(false) => { + metadata.archived_at = None; + } + Some(true) | None => {} + } + if let Err(err) = ctx.upsert_thread(&metadata).await { + warn!( + "state db read-repair upsert failed for {}: {err}", + rollout_path.display() + ); + } else { + return; + } + } + + let default_provider = crate::rollout::list::read_session_meta_line(rollout_path) + .await + .ok() + .and_then(|meta| meta.meta.model_provider) + .unwrap_or_default(); + reconcile_rollout( + Some(ctx), + rollout_path, + default_provider.as_str(), + None, + &[], + archived_only, + ) + .await; +} + /// Apply rollout items incrementally to SQLite. pub async fn apply_rollout_items( context: Option<&codex_state::StateRuntime>, From 68e82e5dc9e7e4cfd006265b657d0b9834e5b27d Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 16:24:18 +0000 Subject: [PATCH 64/90] nit: add DB version is discrepancy recording (#10762) --- codex-rs/core/src/state_db.rs | 7 ++++++- codex-rs/state/src/runtime.rs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index d67a065314..97846cdcf0 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -14,6 +14,7 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionSource; use codex_state::DB_METRIC_COMPARE_ERROR; pub use codex_state::LogEntry; +use codex_state::STATE_DB_VERSION; use codex_state::ThreadMetadataBuilder; use serde_json::Value; use std::path::Path; @@ -504,7 +505,11 @@ pub fn record_discrepancy(stage: &str, reason: &str) { let _ = metric.counter( DB_METRIC_COMPARE_ERROR, 1, - &[("stage", stage), ("reason", reason)], + &[ + ("stage", stage), + ("reason", reason), + ("version", &STATE_DB_VERSION.to_string()), + ], ); } } diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index ae28a02197..384fc57cad 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -38,7 +38,7 @@ use std::time::Duration; use tracing::warn; pub const STATE_DB_FILENAME: &str = "state"; -pub const STATE_DB_VERSION: u32 = 2; +pub const STATE_DB_VERSION: u32 = 3; const METRIC_DB_INIT: &str = "codex.db.init"; From 9ee746afd6273306e5f6e9ed1f7f48ac4e212c23 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 16:39:11 +0000 Subject: [PATCH 65/90] Leverage state DB metadata for thread summaries (#10621) Summary: - read conversation summaries and cwd info from the state DB when possible so we no longer rely on rollout files for metadata and avoid extra I/O - persist CLI version in thread metadata, surface it through summary builders, and add the necessary DB migration hooks - simplify thread listing by using enriched state DB data directly rather than reading rollout heads Testing: - Not run (not requested) --- .../app-server/src/codex_message_processor.rs | 323 ++++++++++++++---- codex-rs/core/src/rollout/list.rs | 140 ++++++-- codex-rs/core/src/rollout/metadata.rs | 1 + codex-rs/core/src/rollout/recorder.rs | 98 ++++-- codex-rs/core/src/rollout/tests.rs | 268 +++++++-------- codex-rs/core/src/session_prefix.rs | 11 - codex-rs/core/tests/suite/cli_stream.rs | 9 +- codex-rs/core/tests/suite/sqlite_state.rs | 6 +- .../migrations/0005_threads_cli_version.sql | 1 + .../0007_threads_first_user_message.sql | 5 + codex-rs/state/src/extract.rs | 82 ++++- codex-rs/state/src/model/thread_metadata.rs | 31 +- codex-rs/state/src/runtime.rs | 22 +- codex-rs/tui/src/resume_picker.rs | 159 ++++----- 14 files changed, 748 insertions(+), 408 deletions(-) create mode 100644 codex-rs/state/migrations/0005_threads_cli_version.sql create mode 100644 codex-rs/state/migrations/0007_threads_first_user_message.sql diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 760b27a53b..6c0956acf5 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -186,7 +186,8 @@ use codex_core::rollout_date_parts; use codex_core::sandboxing::SandboxPermissions; use codex_core::skills::remote::download_remote_skill; use codex_core::skills::remote::list_remote_skills; -use codex_core::state_db::get_state_db; +use codex_core::state_db::StateDbHandle; +use codex_core::state_db::open_if_present; use codex_core::token_data::parse_id_token; use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_feedback::CodexFeedback; @@ -1976,7 +1977,11 @@ impl CodexMessageProcessor { let rollout_path_display = archived_path.display().to_string(); let fallback_provider = self.config.model_provider_id.clone(); - let state_db_ctx = get_state_db(&self.config, None).await; + let state_db_ctx = open_if_present( + &self.config.codex_home, + self.config.model_provider_id.as_str(), + ) + .await; let archived_folder = self .config .codex_home @@ -2285,23 +2290,44 @@ impl CodexMessageProcessor { } }; - let rollout_path = - match find_thread_path_by_id_str(&self.config.codex_home, &thread_uuid.to_string()) - .await - { - Ok(Some(path)) => Some(path), - Ok(None) => None, - Err(err) => { - self.send_invalid_request_error( - request_id, - format!("failed to locate thread id {thread_uuid}: {err}"), - ) - .await; - return; - } - }; + let db_summary = read_summary_from_state_db_by_thread_id(&self.config, thread_uuid).await; + let mut rollout_path = db_summary.as_ref().map(|summary| summary.path.clone()); + if rollout_path.is_none() || include_turns { + rollout_path = + match find_thread_path_by_id_str(&self.config.codex_home, &thread_uuid.to_string()) + .await + { + Ok(Some(path)) => Some(path), + Ok(None) => { + if include_turns { + None + } else { + rollout_path + } + } + Err(err) => { + self.send_invalid_request_error( + request_id, + format!("failed to locate thread id {thread_uuid}: {err}"), + ) + .await; + return; + } + }; + } - let mut thread = if let Some(rollout_path) = rollout_path.as_ref() { + if include_turns && rollout_path.is_none() && db_summary.is_some() { + self.send_internal_error( + request_id, + format!("failed to locate rollout for thread {thread_uuid}"), + ) + .await; + return; + } + + let mut thread = if let Some(summary) = db_summary { + summary_to_thread(summary) + } else if let Some(rollout_path) = rollout_path.as_ref() { let fallback_provider = self.config.model_provider_id.as_str(); match read_summary_from_rollout(rollout_path, fallback_provider).await { Ok(summary) => summary_to_thread(summary), @@ -2608,8 +2634,8 @@ impl CodexMessageProcessor { developer_instructions, } = params; - let rollout_path = if let Some(path) = path { - path + let (rollout_path, source_thread_id) = if let Some(path) = path { + (path, None) } else { let existing_thread_id = match ThreadId::from_string(&thread_id) { Ok(id) => id, @@ -2630,7 +2656,7 @@ impl CodexMessageProcessor { ) .await { - Ok(Some(p)) => p, + Ok(Some(p)) => (p, Some(existing_thread_id)), Ok(None) => { self.send_invalid_request_error( request_id, @@ -2650,14 +2676,9 @@ impl CodexMessageProcessor { } }; - let history_cwd = match read_session_meta_line(&rollout_path).await { - Ok(meta_line) => Some(meta_line.meta.cwd), - Err(err) => { - let rollout_path = rollout_path.display(); - warn!("failed to read session metadata from rollout {rollout_path}: {err}"); - None - } - }; + let history_cwd = + read_history_cwd_from_state_db(&self.config, source_thread_id, rollout_path.as_path()) + .await; // Persist windows sandbox feature. let mut cli_overrides = cli_overrides.unwrap_or_default(); @@ -2807,6 +2828,15 @@ impl CodexMessageProcessor { request_id: RequestId, params: GetConversationSummaryParams, ) { + if let GetConversationSummaryParams::ThreadId { conversation_id } = ¶ms + && let Some(summary) = + read_summary_from_state_db_by_thread_id(&self.config, *conversation_id).await + { + let response = GetConversationSummaryResponse { summary }; + self.outgoing.send_response(request_id, response).await; + return; + } + let path = match params { GetConversationSummaryParams::RolloutPath { rollout_path } => { if rollout_path.is_relative() { @@ -2931,6 +2961,11 @@ impl CodexMessageProcessor { let fallback_provider = self.config.model_provider_id.clone(); let (allowed_sources_vec, source_kind_filter) = compute_source_filters(source_kinds); let allowed_sources = allowed_sources_vec.as_slice(); + let state_db_ctx = open_if_present( + &self.config.codex_home, + self.config.model_provider_id.as_str(), + ) + .await; while remaining > 0 { let page_size = remaining.min(THREAD_LIST_MAX_LIMIT); @@ -2968,31 +3003,26 @@ impl CodexMessageProcessor { })? }; - let mut filtered = page - .items - .into_iter() - .filter_map(|it| { - let updated_at = it.updated_at.clone(); - let session_meta_line = it.head.first().and_then(|first| { - serde_json::from_value::(first.clone()).ok() - })?; - extract_conversation_summary( - it.path, - &it.head, - &session_meta_line.meta, - session_meta_line.git.as_ref(), - fallback_provider.as_str(), - updated_at, - ) - }) - .filter(|summary| { - source_kind_filter - .as_ref() - .is_none_or(|filter| source_kind_matches(&summary.source, filter)) - }) - .collect::>(); - if filtered.len() > remaining { - filtered.truncate(remaining); + let mut filtered = Vec::with_capacity(page.items.len()); + for it in page.items { + let Some(summary) = summary_from_thread_list_item( + it, + fallback_provider.as_str(), + state_db_ctx.as_ref(), + ) + .await + else { + continue; + }; + if source_kind_filter + .as_ref() + .is_none_or(|filter| source_kind_matches(&summary.source, filter)) + { + filtered.push(summary); + if filtered.len() >= remaining { + break; + } + } } items.extend(filtered); remaining = requested_page_size.saturating_sub(items.len()); @@ -3581,13 +3611,13 @@ impl CodexMessageProcessor { } = params; // Derive a Config using the same logic as new conversation, honoring overrides if provided. - let rollout_path = if let Some(path) = path { - path + let (rollout_path, source_thread_id) = if let Some(path) = path { + (path, None) } else if let Some(conversation_id) = conversation_id { match find_thread_path_by_id_str(&self.config.codex_home, &conversation_id.to_string()) .await { - Ok(Some(found_path)) => found_path, + Ok(Some(found_path)) => (found_path, Some(conversation_id)), Ok(None) => { self.send_invalid_request_error( request_id, @@ -3614,14 +3644,9 @@ impl CodexMessageProcessor { return; }; - let history_cwd = match read_session_meta_line(&rollout_path).await { - Ok(meta_line) => Some(meta_line.meta.cwd), - Err(err) => { - let rollout_path = rollout_path.display(); - warn!("failed to read session metadata from rollout {rollout_path}: {err}"); - None - } - }; + let history_cwd = + read_history_cwd_from_state_db(&self.config, source_thread_id, rollout_path.as_path()) + .await; let (typesafe_overrides, request_overrides) = match overrides { Some(overrides) => { @@ -3909,7 +3934,11 @@ impl CodexMessageProcessor { } if state_db_ctx.is_none() { - state_db_ctx = get_state_db(&self.config, None).await; + state_db_ctx = open_if_present( + &self.config.codex_home, + self.config.model_provider_id.as_str(), + ) + .await; } // Move the rollout file to archived. @@ -5094,6 +5123,168 @@ async fn derive_config_for_cwd( .await } +async fn read_history_cwd_from_state_db( + config: &Config, + thread_id: Option, + rollout_path: &Path, +) -> Option { + if let Some(state_db_ctx) = + open_if_present(&config.codex_home, config.model_provider_id.as_str()).await + && let Some(thread_id) = thread_id + && let Ok(Some(metadata)) = state_db_ctx.get_thread(thread_id).await + { + return Some(metadata.cwd); + } + + match read_session_meta_line(rollout_path).await { + Ok(meta_line) => Some(meta_line.meta.cwd), + Err(err) => { + let rollout_path = rollout_path.display(); + warn!("failed to read session metadata from rollout {rollout_path}: {err}"); + None + } + } +} + +async fn read_summary_from_state_db_by_thread_id( + config: &Config, + thread_id: ThreadId, +) -> Option { + let state_db_ctx = open_if_present(&config.codex_home, config.model_provider_id.as_str()).await; + read_summary_from_state_db_context_by_thread_id(state_db_ctx.as_ref(), thread_id).await +} + +async fn read_summary_from_state_db_context_by_thread_id( + state_db_ctx: Option<&StateDbHandle>, + thread_id: ThreadId, +) -> Option { + let state_db_ctx = state_db_ctx?; + + let metadata = match state_db_ctx.get_thread(thread_id).await { + Ok(Some(metadata)) => metadata, + Ok(None) | Err(_) => return None, + }; + Some(summary_from_state_db_metadata( + metadata.id, + metadata.rollout_path, + metadata.first_user_message, + metadata + .created_at + .to_rfc3339_opts(SecondsFormat::Secs, true), + metadata + .updated_at + .to_rfc3339_opts(SecondsFormat::Secs, true), + metadata.model_provider, + metadata.cwd, + metadata.cli_version, + metadata.source, + metadata.git_sha, + metadata.git_branch, + metadata.git_origin_url, + )) +} + +async fn summary_from_thread_list_item( + it: codex_core::ThreadItem, + fallback_provider: &str, + state_db_ctx: Option<&StateDbHandle>, +) -> Option { + if let Some(thread_id) = it.thread_id { + let timestamp = it.created_at.clone(); + let updated_at = it.updated_at.clone().or_else(|| timestamp.clone()); + let model_provider = it + .model_provider + .clone() + .unwrap_or_else(|| fallback_provider.to_string()); + let cwd = it.cwd?; + let cli_version = it.cli_version.unwrap_or_default(); + let source = it + .source + .unwrap_or(codex_protocol::protocol::SessionSource::Unknown); + return Some(ConversationSummary { + conversation_id: thread_id, + path: it.path, + preview: it.first_user_message.unwrap_or_default(), + timestamp, + updated_at, + model_provider, + cwd, + cli_version, + source, + git_info: if it.git_sha.is_none() + && it.git_branch.is_none() + && it.git_origin_url.is_none() + { + None + } else { + Some(ConversationGitInfo { + sha: it.git_sha, + branch: it.git_branch, + origin_url: it.git_origin_url, + }) + }, + }); + } + if let Some(thread_id) = thread_id_from_rollout_path(it.path.as_path()) { + return read_summary_from_state_db_context_by_thread_id(state_db_ctx, thread_id).await; + } + None +} + +fn thread_id_from_rollout_path(path: &Path) -> Option { + let file_name = path.file_name()?.to_str()?; + let stem = file_name.strip_suffix(".jsonl")?; + if stem.len() < 37 { + return None; + } + let uuid_start = stem.len().saturating_sub(36); + if !stem[..uuid_start].ends_with('-') { + return None; + } + ThreadId::from_string(&stem[uuid_start..]).ok() +} + +#[allow(clippy::too_many_arguments)] +fn summary_from_state_db_metadata( + conversation_id: ThreadId, + path: PathBuf, + first_user_message: Option, + timestamp: String, + updated_at: String, + model_provider: String, + cwd: PathBuf, + cli_version: String, + source: String, + git_sha: Option, + git_branch: Option, + git_origin_url: Option, +) -> ConversationSummary { + let preview = first_user_message.unwrap_or_default(); + let source = serde_json::from_value(serde_json::Value::String(source)) + .unwrap_or(codex_protocol::protocol::SessionSource::Unknown); + let git_info = if git_sha.is_none() && git_branch.is_none() && git_origin_url.is_none() { + None + } else { + Some(ConversationGitInfo { + sha: git_sha, + branch: git_branch, + origin_url: git_origin_url, + }) + }; + ConversationSummary { + conversation_id, + path, + preview, + timestamp: Some(timestamp), + updated_at: Some(updated_at), + model_provider, + cwd, + cli_version, + source, + git_info, + } +} + pub(crate) async fn read_summary_from_rollout( path: &Path, fallback_provider: &str, diff --git a/codex-rs/core/src/rollout/list.rs b/codex-rs/core/src/rollout/list.rs index 092bb22c65..940402902e 100644 --- a/codex-rs/core/src/rollout/list.rs +++ b/codex-rs/core/src/rollout/list.rs @@ -15,9 +15,7 @@ use uuid::Uuid; use super::ARCHIVED_SESSIONS_SUBDIR; use super::SESSIONS_SUBDIR; -use crate::instructions::UserInstructions; use crate::protocol::EventMsg; -use crate::session_prefix::is_session_prefix_content; use crate::state_db; use codex_file_search as file_search; use codex_protocol::ThreadId; @@ -25,6 +23,7 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; +use codex_protocol::protocol::USER_MESSAGE_BEGIN; /// Returned page of thread (thread) summaries. #[derive(Debug, Default, PartialEq)] @@ -44,8 +43,24 @@ pub struct ThreadsPage { pub struct ThreadItem { /// Absolute path to the rollout file. pub path: PathBuf, - /// First up to `HEAD_RECORD_LIMIT` JSONL records parsed as JSON (includes meta line). - pub head: Vec, + /// Thread ID from session metadata. + pub thread_id: Option, + /// First user message captured for this thread, if any. + pub first_user_message: Option, + /// Working directory from session metadata. + pub cwd: Option, + /// Git branch from session metadata. + pub git_branch: Option, + /// Git commit SHA from session metadata. + pub git_sha: Option, + /// Git origin URL from session metadata. + pub git_origin_url: Option, + /// Session source from session metadata. + pub source: Option, + /// Model provider from session metadata. + pub model_provider: Option, + /// CLI version from session metadata. + pub cli_version: Option, /// RFC3339 timestamp string for when the session was created, if available. /// created_at comes from the filename timestamp with second precision. pub created_at: Option, @@ -63,11 +78,17 @@ pub type ConversationsPage = ThreadsPage; #[derive(Default)] struct HeadTailSummary { - head: Vec, saw_session_meta: bool, saw_user_event: bool, + thread_id: Option, + first_user_message: Option, + cwd: Option, + git_branch: Option, + git_sha: Option, + git_origin_url: Option, source: Option, model_provider: Option, + cli_version: Option, created_at: Option, updated_at: Option, } @@ -674,7 +695,8 @@ async fn build_thread_item( if !allowed_sources.is_empty() && !summary .source - .is_some_and(|source| allowed_sources.contains(&source)) + .as_ref() + .is_some_and(|source| allowed_sources.contains(source)) { return None; } @@ -686,7 +708,15 @@ async fn build_thread_item( // Apply filters: must have session meta and at least one user message event if summary.saw_session_meta && summary.saw_user_event { let HeadTailSummary { - head, + thread_id, + first_user_message, + cwd, + git_branch, + git_sha, + git_origin_url, + source, + model_provider, + cli_version, created_at, updated_at: mut summary_updated_at, .. @@ -696,7 +726,15 @@ async fn build_thread_item( } return Some(ThreadItem { path, - head, + thread_id, + first_user_message, + cwd, + git_branch, + git_sha, + git_origin_url, + source, + model_provider, + cli_version, created_at, updated_at: summary_updated_at, }); @@ -979,31 +1017,29 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result { summary.source = Some(session_meta_line.meta.source.clone()); summary.model_provider = session_meta_line.meta.model_provider.clone(); + summary.thread_id = Some(session_meta_line.meta.id); + summary.cwd = Some(session_meta_line.meta.cwd.clone()); + summary.git_branch = session_meta_line + .git + .as_ref() + .and_then(|git| git.branch.clone()); + summary.git_sha = session_meta_line + .git + .as_ref() + .and_then(|git| git.commit_hash.clone()); + summary.git_origin_url = session_meta_line + .git + .as_ref() + .and_then(|git| git.repository_url.clone()); + summary.cli_version = Some(session_meta_line.meta.cli_version); summary.created_at = Some(session_meta_line.meta.timestamp.clone()); summary.saw_session_meta = true; - if summary.head.len() < head_limit - && let Ok(val) = serde_json::to_value(session_meta_line) - { - summary.head.push(val); - } } - RolloutItem::ResponseItem(item) => { + RolloutItem::ResponseItem(_) => { summary.created_at = summary .created_at .clone() .or_else(|| Some(rollout_line.timestamp.clone())); - if let codex_protocol::models::ResponseItem::Message { role, content, .. } = &item - && role == "user" - && !UserInstructions::is_user_instructions(content.as_slice()) - && !is_session_prefix_content(content.as_slice()) - { - summary.saw_user_event = true; - } - if summary.head.len() < head_limit - && let Ok(val) = serde_json::to_value(item) - { - summary.head.push(val); - } } RolloutItem::TurnContext(_) => { // Not included in `head`; skip. @@ -1012,8 +1048,14 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result { - if matches!(ev, EventMsg::UserMessage(_)) { + if let EventMsg::UserMessage(user) = ev { summary.saw_user_event = true; + if summary.first_user_message.is_none() { + let message = strip_user_message_prefix(user.message.as_str()).to_string(); + if !message.is_empty() { + summary.first_user_message = Some(message); + } + } } } } @@ -1029,8 +1071,48 @@ async fn read_head_summary(path: &Path, head_limit: usize) -> io::Result io::Result> { - let summary = read_head_summary(path, HEAD_RECORD_LIMIT).await?; - Ok(summary.head) + use tokio::io::AsyncBufReadExt; + + let file = tokio::fs::File::open(path).await?; + let reader = tokio::io::BufReader::new(file); + let mut lines = reader.lines(); + let mut head = Vec::new(); + + while head.len() < HEAD_RECORD_LIMIT { + let Some(line) = lines.next_line().await? else { + break; + }; + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + if let Ok(rollout_line) = serde_json::from_str::(trimmed) { + match rollout_line.item { + RolloutItem::SessionMeta(session_meta_line) => { + if let Ok(value) = serde_json::to_value(session_meta_line) { + head.push(value); + } + } + RolloutItem::ResponseItem(item) => { + if let Ok(value) = serde_json::to_value(item) { + head.push(value); + } + } + RolloutItem::Compacted(_) + | RolloutItem::TurnContext(_) + | RolloutItem::EventMsg(_) => {} + } + } + } + + Ok(head) +} + +fn strip_user_message_prefix(text: &str) -> &str { + match text.find(USER_MESSAGE_BEGIN) { + Some(idx) => text[idx + USER_MESSAGE_BEGIN.len()..].trim(), + None => text.trim(), + } } /// Read the SessionMetaLine from the head of a rollout file for reuse by diff --git a/codex-rs/core/src/rollout/metadata.rs b/codex-rs/core/src/rollout/metadata.rs index b87879f4c7..dc87c74da4 100644 --- a/codex-rs/core/src/rollout/metadata.rs +++ b/codex-rs/core/src/rollout/metadata.rs @@ -44,6 +44,7 @@ pub(crate) fn builder_from_session_meta( ); builder.model_provider = session_meta.meta.model_provider.clone(); builder.cwd = session_meta.meta.cwd.clone(); + builder.cli_version = Some(session_meta.meta.cli_version.clone()); builder.sandbox_policy = SandboxPolicy::ReadOnly; builder.approval_mode = AskForApproval::OnRequest; if let Some(git) = session_meta.git.as_ref() { diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index 0817577b28..c42bbb7974 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -32,7 +32,6 @@ use super::list::ThreadSortKey; use super::list::ThreadsPage; use super::list::get_threads; use super::list::get_threads_in_root; -use super::list::read_head_for_summary; use super::metadata; use super::policy::is_persisted_response_item; use crate::config::Config; @@ -184,9 +183,7 @@ impl RolloutRecorder { ) .await { - let mut page: ThreadsPage = db_page.into(); - populate_thread_heads(page.items.as_mut_slice()).await; - return Ok(page); + return Ok(db_page.into()); } tracing::error!("Falling back on rollout system"); state_db::record_discrepancy("list_threads_with_db_fallback", "falling_back"); @@ -232,9 +229,37 @@ impl RolloutRecorder { default_provider: &str, filter_cwd: Option<&Path>, ) -> std::io::Result> { + let state_db_ctx = state_db::open_if_present(codex_home, default_provider).await; + if state_db_ctx.is_some() { + let mut db_cursor = cursor.cloned(); + loop { + let Some(db_page) = state_db::list_threads_db( + state_db_ctx.as_deref(), + codex_home, + page_size, + db_cursor.as_ref(), + sort_key, + allowed_sources, + model_providers, + false, + ) + .await + else { + break; + }; + if let Some(path) = select_resume_path_from_db_page(&db_page, filter_cwd) { + return Ok(Some(path)); + } + db_cursor = db_page.next_anchor.map(Into::into); + if db_cursor.is_none() { + break; + } + } + } + let mut cursor = cursor.cloned(); loop { - let page = Self::list_threads( + let page = get_threads( codex_home, page_size, cursor.as_ref(), @@ -659,7 +684,18 @@ impl From for ThreadsPage { .into_iter() .map(|item| ThreadItem { path: item.rollout_path, - head: Vec::new(), + thread_id: Some(item.id), + first_user_message: item.first_user_message, + cwd: Some(item.cwd), + git_branch: item.git_branch, + git_sha: item.git_sha, + git_origin_url: item.git_origin_url, + source: Some( + serde_json::from_value(Value::String(item.source)) + .unwrap_or(SessionSource::Unknown), + ), + model_provider: Some(item.model_provider), + cli_version: Some(item.cli_version), created_at: Some(item.created_at.to_rfc3339_opts(SecondsFormat::Secs, true)), updated_at: Some(item.updated_at.to_rfc3339_opts(SecondsFormat::Secs, true)), }) @@ -673,24 +709,14 @@ impl From for ThreadsPage { } } -async fn populate_thread_heads(items: &mut [ThreadItem]) { - for item in items { - item.head = read_head_for_summary(item.path.as_path()) - .await - .unwrap_or_else(|err| { - warn!( - "failed to read rollout head from state db path: {} ({err})", - item.path.display() - ); - Vec::new() - }); - } -} - fn select_resume_path(page: &ThreadsPage, filter_cwd: Option<&Path>) -> Option { match filter_cwd { Some(cwd) => page.items.iter().find_map(|item| { - if session_cwd_matches(&item.head, cwd) { + if item + .cwd + .as_ref() + .is_some_and(|session_cwd| cwd_matches(session_cwd, cwd)) + { Some(item.path.clone()) } else { None @@ -700,22 +726,28 @@ fn select_resume_path(page: &ThreadsPage, filter_cwd: Option<&Path>) -> Option

bool { - let Some(session_cwd) = extract_session_cwd(head) else { - return false; - }; +fn select_resume_path_from_db_page( + page: &codex_state::ThreadsPage, + filter_cwd: Option<&Path>, +) -> Option { + match filter_cwd { + Some(cwd) => page.items.iter().find_map(|item| { + if cwd_matches(item.cwd.as_path(), cwd) { + Some(item.rollout_path.clone()) + } else { + None + } + }), + None => page.items.first().map(|item| item.rollout_path.clone()), + } +} + +fn cwd_matches(session_cwd: &Path, cwd: &Path) -> bool { if let (Ok(ca), Ok(cb)) = ( - path_utils::normalize_for_path_comparison(&session_cwd), + path_utils::normalize_for_path_comparison(session_cwd), path_utils::normalize_for_path_comparison(cwd), ) { return ca == cb; } session_cwd == cwd } - -fn extract_session_cwd(head: &[serde_json::Value]) -> Option { - head.iter().find_map(|value| { - let meta_line = serde_json::from_value::(value.clone()).ok()?; - Some(meta_line.meta.cwd) - }) -} diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index c4d93415ba..ccd476b8cf 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -23,6 +23,7 @@ use crate::rollout::list::ThreadItem; use crate::rollout::list::ThreadSortKey; use crate::rollout::list::ThreadsPage; use crate::rollout::list::get_threads; +use crate::rollout::list::read_head_for_summary; use crate::rollout::recorder::RolloutRecorder; use crate::rollout::rollout_date_parts; use anyhow::Result; @@ -47,6 +48,10 @@ fn provider_vec(providers: &[&str]) -> Vec { .collect() } +fn thread_id_from_uuid(uuid: Uuid) -> ThreadId { + ThreadId::from_string(&uuid.to_string()).expect("valid thread id") +} + async fn insert_state_db_thread( home: &Path, thread_id: ThreadId, @@ -73,7 +78,7 @@ async fn insert_state_db_thread( builder.archived_at = Some(created_at); } let mut metadata = builder.build(TEST_PROVIDER); - metadata.has_user_event = true; + metadata.first_user_message = Some("Hello from user".to_string()); runtime .upsert_thread(&metadata) .await @@ -115,6 +120,12 @@ async fn list_threads_prefers_state_db_when_available() { assert_eq!(page.items.len(), 1); assert_eq!(page.items[0].path, db_rollout_path); + assert_eq!(page.items[0].thread_id, Some(db_thread_id)); + assert_eq!(page.items[0].cwd, Some(home.to_path_buf())); + assert_eq!( + page.items[0].first_user_message.as_deref(), + Some("Hello from user") + ); } #[tokio::test] @@ -559,37 +570,6 @@ async fn test_list_conversations_latest_first() { .join("01") .join(format!("rollout-2025-01-01T12-00-00-{u1}.jsonl")); - let head_3 = vec![serde_json::json!({ - "id": u3, - "timestamp": "2025-01-03T12-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; - let head_2 = vec![serde_json::json!({ - "id": u2, - "timestamp": "2025-01-02T12-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; - let head_1 = vec![serde_json::json!({ - "id": u1, - "timestamp": "2025-01-01T12-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; - let updated_times: Vec> = page.items.iter().map(|i| i.updated_at.clone()).collect(); @@ -597,19 +577,43 @@ async fn test_list_conversations_latest_first() { items: vec![ ThreadItem { path: p1, - head: head_3, + thread_id: Some(thread_id_from_uuid(u3)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-01-03T12-00-00".into()), updated_at: updated_times.first().cloned().flatten(), }, ThreadItem { path: p2, - head: head_2, + thread_id: Some(thread_id_from_uuid(u2)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-01-02T12-00-00".into()), updated_at: updated_times.get(1).cloned().flatten(), }, ThreadItem { path: p3, - head: head_1, + thread_id: Some(thread_id_from_uuid(u1)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-01-01T12-00-00".into()), updated_at: updated_times.get(2).cloned().flatten(), }, @@ -700,26 +704,6 @@ async fn test_pagination_cursor() { .join("03") .join("04") .join(format!("rollout-2025-03-04T09-00-00-{u4}.jsonl")); - let head_5 = vec![serde_json::json!({ - "id": u5, - "timestamp": "2025-03-05T09-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; - let head_4 = vec![serde_json::json!({ - "id": u4, - "timestamp": "2025-03-04T09-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; let updated_page1: Vec> = page1.items.iter().map(|i| i.updated_at.clone()).collect(); let expected_cursor1: Cursor = @@ -728,13 +712,29 @@ async fn test_pagination_cursor() { items: vec![ ThreadItem { path: p5, - head: head_5, + thread_id: Some(thread_id_from_uuid(u5)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-03-05T09-00-00".into()), updated_at: updated_page1.first().cloned().flatten(), }, ThreadItem { path: p4, - head: head_4, + thread_id: Some(thread_id_from_uuid(u4)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-03-04T09-00-00".into()), updated_at: updated_page1.get(1).cloned().flatten(), }, @@ -768,26 +768,6 @@ async fn test_pagination_cursor() { .join("03") .join("02") .join(format!("rollout-2025-03-02T09-00-00-{u2}.jsonl")); - let head_3 = vec![serde_json::json!({ - "id": u3, - "timestamp": "2025-03-03T09-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; - let head_2 = vec![serde_json::json!({ - "id": u2, - "timestamp": "2025-03-02T09-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; let updated_page2: Vec> = page2.items.iter().map(|i| i.updated_at.clone()).collect(); let expected_cursor2: Cursor = @@ -796,13 +776,29 @@ async fn test_pagination_cursor() { items: vec![ ThreadItem { path: p3, - head: head_3, + thread_id: Some(thread_id_from_uuid(u3)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-03-03T09-00-00".into()), updated_at: updated_page2.first().cloned().flatten(), }, ThreadItem { path: p2, - head: head_2, + thread_id: Some(thread_id_from_uuid(u2)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-03-02T09-00-00".into()), updated_at: updated_page2.get(1).cloned().flatten(), }, @@ -830,22 +826,20 @@ async fn test_pagination_cursor() { .join("03") .join("01") .join(format!("rollout-2025-03-01T09-00-00-{u1}.jsonl")); - let head_1 = vec![serde_json::json!({ - "id": u1, - "timestamp": "2025-03-01T09-00-00", - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; let updated_page3: Vec> = page3.items.iter().map(|i| i.updated_at.clone()).collect(); let expected_page3 = ThreadsPage { items: vec![ThreadItem { path: p1, - head: head_1, + thread_id: Some(thread_id_from_uuid(u1)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some("2025-03-01T09-00-00".into()), updated_at: updated_page3.first().cloned().flatten(), }], @@ -913,20 +907,18 @@ async fn test_get_thread_contents() { .join("04") .join("01") .join(format!("rollout-2025-04-01T10-30-00-{uuid}.jsonl")); - let expected_head = vec![serde_json::json!({ - "id": uuid, - "timestamp": ts, - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })]; let expected_page = ThreadsPage { items: vec![ThreadItem { path: expected_path, - head: expected_head, + thread_id: Some(thread_id_from_uuid(uuid)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some(ts.into()), updated_at: page.items[0].updated_at.clone(), }], @@ -993,13 +985,12 @@ async fn test_base_instructions_missing_in_meta_defaults_to_null() { .await .unwrap(); - let head = page - .items - .first() - .and_then(|item| item.head.first()) + let head = read_head_for_summary(&page.items[0].path) + .await .expect("session meta head"); + let first = head.first().expect("first head entry"); assert_eq!( - head.get("base_instructions"), + first.get("base_instructions"), Some(&serde_json::Value::Null) ); } @@ -1037,12 +1028,11 @@ async fn test_base_instructions_present_in_meta_is_preserved() { .await .unwrap(); - let head = page - .items - .first() - .and_then(|item| item.head.first()) + let head = read_head_for_summary(&page.items[0].path) + .await .expect("session meta head"); - let base = head + let first = head.first().expect("first head entry"); + let base = first .get("base_instructions") .and_then(|value| value.get("text")) .and_then(serde_json::Value::as_str); @@ -1222,18 +1212,6 @@ async fn test_stable_ordering_same_second_pagination() { .join("07") .join("01") .join(format!("rollout-2025-07-01T00-00-00-{u2}.jsonl")); - let head = |u: Uuid| -> Vec { - vec![serde_json::json!({ - "id": u, - "timestamp": ts, - "cwd": ".", - "originator": "test_originator", - "cli_version": "test_version", - "source": "vscode", - "model_provider": "test-provider", - "base_instructions": null, - })] - }; let updated_page1: Vec> = page1.items.iter().map(|i| i.updated_at.clone()).collect(); let expected_cursor1: Cursor = serde_json::from_str(&format!("\"{ts}|{u2}\"")).unwrap(); @@ -1241,13 +1219,29 @@ async fn test_stable_ordering_same_second_pagination() { items: vec![ ThreadItem { path: p3, - head: head(u3), + thread_id: Some(thread_id_from_uuid(u3)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some(ts.to_string()), updated_at: updated_page1.first().cloned().flatten(), }, ThreadItem { path: p2, - head: head(u2), + thread_id: Some(thread_id_from_uuid(u2)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some(ts.to_string()), updated_at: updated_page1.get(1).cloned().flatten(), }, @@ -1280,7 +1274,15 @@ async fn test_stable_ordering_same_second_pagination() { let expected_page2 = ThreadsPage { items: vec![ThreadItem { path: p1, - head: head(u1), + thread_id: Some(thread_id_from_uuid(u1)), + first_user_message: Some("Hello from user".to_string()), + cwd: Some(Path::new(".").to_path_buf()), + git_branch: None, + git_sha: None, + git_origin_url: None, + source: Some(SessionSource::VSCode), + model_provider: Some(TEST_PROVIDER.to_string()), + cli_version: Some("test_version".to_string()), created_at: Some(ts.to_string()), updated_at: updated_page2.first().cloned().flatten(), }], @@ -1415,13 +1417,7 @@ async fn test_model_provider_filter_selects_only_matching_sessions() -> Result<( let openai_ids: Vec<_> = openai_sessions .items .iter() - .filter_map(|item| { - item.head - .first() - .and_then(|value| value.get("id")) - .and_then(serde_json::Value::as_str) - .map(str::to_string) - }) + .filter_map(|item| item.thread_id.as_ref().map(ToString::to_string)) .collect(); assert!(openai_ids.contains(&openai_id_str)); assert!(openai_ids.contains(&none_id_str)); @@ -1442,10 +1438,8 @@ async fn test_model_provider_filter_selects_only_matching_sessions() -> Result<( let beta_head = beta_sessions .items .first() - .and_then(|item| item.head.first()) - .and_then(|value| value.get("id")) - .and_then(serde_json::Value::as_str); - assert_eq!(beta_head, Some(beta_id_str.as_str())); + .and_then(|item| item.thread_id.as_ref().map(ToString::to_string)); + assert_eq!(beta_head.as_deref(), Some(beta_id_str.as_str())); let unknown_filter = provider_vec(&["unknown"]); let unknown_sessions = get_threads( diff --git a/codex-rs/core/src/session_prefix.rs b/codex-rs/core/src/session_prefix.rs index 198f5dff90..99283082b6 100644 --- a/codex-rs/core/src/session_prefix.rs +++ b/codex-rs/core/src/session_prefix.rs @@ -1,5 +1,3 @@ -use codex_protocol::models::ContentItem; - /// Helpers for identifying model-visible "session prefix" messages. /// /// A session prefix is a user-role message that carries configuration or state needed by @@ -15,12 +13,3 @@ pub(crate) fn is_session_prefix(text: &str) -> bool { let lowered = trimmed.to_ascii_lowercase(); lowered.starts_with(ENVIRONMENT_CONTEXT_OPEN_TAG) || lowered.starts_with(TURN_ABORTED_OPEN_TAG) } - -/// Returns true if `text` starts with a session prefix marker (case-insensitive). -pub(crate) fn is_session_prefix_content(content: &[ContentItem]) -> bool { - if let [ContentItem::InputText { text }] = content { - is_session_prefix(text) - } else { - false - } -} diff --git a/codex-rs/core/tests/suite/cli_stream.rs b/codex-rs/core/tests/suite/cli_stream.rs index 291f596e27..106e2ff148 100644 --- a/codex-rs/core/tests/suite/cli_stream.rs +++ b/codex-rs/core/tests/suite/cli_stream.rs @@ -85,13 +85,8 @@ async fn responses_mode_stream_cli() { !page.items.is_empty(), "expected at least one session to be listed" ); - // First line of head must be the SessionMeta payload (id/timestamp) - let head0 = page.items[0].head.first().expect("missing head record"); - assert!(head0.get("id").is_some(), "head[0] missing id"); - assert!( - head0.get("timestamp").is_some(), - "head[0] missing timestamp" - ); + assert!(page.items[0].thread_id.is_some(), "missing thread_id"); + assert!(page.items[0].created_at.is_some(), "missing created_at"); } /// Verify that passing `-c model_instructions_file=...` to the CLI diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index 07268f4fdb..688b034ac7 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -169,7 +169,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { assert_eq!(metadata.id, thread_id); assert_eq!(metadata.rollout_path, rollout_path); assert_eq!(metadata.model_provider, default_provider); - assert!(metadata.has_user_event); + assert!(metadata.first_user_message.is_some()); let mut stored_tools = None; for _ in 0..40 { @@ -221,7 +221,7 @@ async fn user_messages_persist_in_state_db() -> Result<()> { metadata = db.get_thread(thread_id).await?; if metadata .as_ref() - .map(|entry| entry.has_user_event) + .map(|entry| entry.first_user_message.is_some()) .unwrap_or(false) { break; @@ -230,7 +230,7 @@ async fn user_messages_persist_in_state_db() -> Result<()> { } let metadata = metadata.expect("thread should exist in state db"); - assert!(metadata.has_user_event); + assert!(metadata.first_user_message.is_some()); Ok(()) } diff --git a/codex-rs/state/migrations/0005_threads_cli_version.sql b/codex-rs/state/migrations/0005_threads_cli_version.sql new file mode 100644 index 0000000000..8891562d90 --- /dev/null +++ b/codex-rs/state/migrations/0005_threads_cli_version.sql @@ -0,0 +1 @@ +ALTER TABLE threads ADD COLUMN cli_version TEXT NOT NULL DEFAULT ''; diff --git a/codex-rs/state/migrations/0007_threads_first_user_message.sql b/codex-rs/state/migrations/0007_threads_first_user_message.sql new file mode 100644 index 0000000000..5e9a7649bb --- /dev/null +++ b/codex-rs/state/migrations/0007_threads_first_user_message.sql @@ -0,0 +1,5 @@ +ALTER TABLE threads ADD COLUMN first_user_message TEXT NOT NULL DEFAULT ''; + +UPDATE threads +SET first_user_message = title +WHERE first_user_message = '' AND has_user_event = 1 AND title <> ''; diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index 753d8e426d..f8f9cb5251 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -5,9 +5,12 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::TurnContextItem; use codex_protocol::protocol::USER_MESSAGE_BEGIN; +use codex_protocol::protocol::UserMessageEvent; use serde::Serialize; use serde_json::Value; +const IMAGE_ONLY_USER_MESSAGE_PLACEHOLDER: &str = "[Image]"; + /// Apply a rollout item to the metadata structure. pub fn apply_rollout_item( metadata: &mut ThreadMetadata, @@ -37,6 +40,9 @@ fn apply_session_meta_from_item(metadata: &mut ThreadMetadata, meta_line: &Sessi if let Some(provider) = meta_line.meta.model_provider.as_deref() { metadata.model_provider = provider.to_string(); } + if !meta_line.meta.cli_version.is_empty() { + metadata.cli_version = meta_line.meta.cli_version.clone(); + } if !meta_line.meta.cwd.as_os_str().is_empty() { metadata.cwd = meta_line.meta.cwd.clone(); } @@ -61,9 +67,14 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } } EventMsg::UserMessage(user) => { - metadata.has_user_event = true; + if metadata.first_user_message.is_none() { + metadata.first_user_message = user_message_preview(user); + } if metadata.title.is_empty() { - metadata.title = strip_user_message_prefix(user.message.as_str()).to_string(); + let title = strip_user_message_prefix(user.message.as_str()); + if !title.is_empty() { + metadata.title = title.to_string(); + } } } _ => {} @@ -71,7 +82,7 @@ fn apply_event_msg(metadata: &mut ThreadMetadata, event: &EventMsg) { } fn apply_response_item(_metadata: &mut ThreadMetadata, _item: &ResponseItem) { - // Title and has_user_event are derived from EventMsg::UserMessage only. + // Title and first_user_message are derived from EventMsg::UserMessage only. } fn strip_user_message_prefix(text: &str) -> &str { @@ -81,6 +92,22 @@ fn strip_user_message_prefix(text: &str) -> &str { } } +fn user_message_preview(user: &UserMessageEvent) -> Option { + let message = strip_user_message_prefix(user.message.as_str()); + if !message.is_empty() { + return Some(message.to_string()); + } + if user + .images + .as_ref() + .is_some_and(|images| !images.is_empty()) + || !user.local_images.is_empty() + { + return Some(IMAGE_ONLY_USER_MESSAGE_PLACEHOLDER.to_string()); + } + None +} + pub(crate) fn enum_to_string(value: &T) -> String { match serde_json::to_value(value) { Ok(Value::String(s)) => s, @@ -108,7 +135,7 @@ mod tests { use uuid::Uuid; #[test] - fn response_item_user_messages_do_not_set_title_or_has_user_event() { + fn response_item_user_messages_do_not_set_title_or_first_user_message() { let mut metadata = metadata_for_test(); let item = RolloutItem::ResponseItem(ResponseItem::Message { id: None, @@ -122,12 +149,12 @@ mod tests { apply_rollout_item(&mut metadata, &item, "test-provider"); - assert_eq!(metadata.has_user_event, false); + assert_eq!(metadata.first_user_message, None); assert_eq!(metadata.title, ""); } #[test] - fn event_msg_user_messages_set_title_and_has_user_event() { + fn event_msg_user_messages_set_title_and_first_user_message() { let mut metadata = metadata_for_test(); let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { message: format!("{USER_MESSAGE_BEGIN} actual user request"), @@ -138,10 +165,48 @@ mod tests { apply_rollout_item(&mut metadata, &item, "test-provider"); - assert_eq!(metadata.has_user_event, true); + assert_eq!( + metadata.first_user_message.as_deref(), + Some("actual user request") + ); assert_eq!(metadata.title, "actual user request"); } + #[test] + fn event_msg_image_only_user_message_sets_image_placeholder_preview() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: String::new(), + images: Some(vec!["https://example.com/image.png".to_string()]), + local_images: vec![], + text_elements: vec![], + })); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!( + metadata.first_user_message.as_deref(), + Some(super::IMAGE_ONLY_USER_MESSAGE_PLACEHOLDER) + ); + assert_eq!(metadata.title, ""); + } + + #[test] + fn event_msg_blank_user_message_without_images_keeps_first_user_message_empty() { + let mut metadata = metadata_for_test(); + let item = RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: " ".to_string(), + images: Some(vec![]), + local_images: vec![], + text_elements: vec![], + })); + + apply_rollout_item(&mut metadata, &item, "test-provider"); + + assert_eq!(metadata.first_user_message, None); + assert_eq!(metadata.title, ""); + } + fn metadata_for_test() -> ThreadMetadata { let id = ThreadId::from_string(&Uuid::from_u128(42).to_string()).expect("thread id"); let created_at = DateTime::::from_timestamp(1_735_689_600, 0).expect("timestamp"); @@ -153,11 +218,12 @@ mod tests { source: "cli".to_string(), model_provider: "openai".to_string(), cwd: PathBuf::from("/tmp"), + cli_version: "0.0.0".to_string(), title: String::new(), sandbox_policy: "read-only".to_string(), approval_mode: "on-request".to_string(), tokens_used: 1, - has_user_event: false, + first_user_message: None, archived_at: None, git_sha: None, git_branch: None, diff --git a/codex-rs/state/src/model/thread_metadata.rs b/codex-rs/state/src/model/thread_metadata.rs index 7d475efffc..2577ead502 100644 --- a/codex-rs/state/src/model/thread_metadata.rs +++ b/codex-rs/state/src/model/thread_metadata.rs @@ -66,6 +66,8 @@ pub struct ThreadMetadata { pub model_provider: String, /// The working directory for the thread. pub cwd: PathBuf, + /// Version of the CLI that created the thread. + pub cli_version: String, /// A best-effort thread title. pub title: String, /// The sandbox policy (stringified enum). @@ -74,8 +76,8 @@ pub struct ThreadMetadata { pub approval_mode: String, /// The last observed token usage. pub tokens_used: i64, - /// Whether the thread has observed a user message. - pub has_user_event: bool, + /// First user message observed for this thread, if any. + pub first_user_message: Option, /// The archive timestamp, if the thread is archived. pub archived_at: Option>, /// The git commit SHA, if known. @@ -103,6 +105,8 @@ pub struct ThreadMetadataBuilder { pub model_provider: Option, /// The working directory for the thread. pub cwd: PathBuf, + /// Version of the CLI that created the thread. + pub cli_version: Option, /// The sandbox policy. pub sandbox_policy: SandboxPolicy, /// The approval mode. @@ -133,6 +137,7 @@ impl ThreadMetadataBuilder { source, model_provider: None, cwd: PathBuf::new(), + cli_version: None, sandbox_policy: SandboxPolicy::ReadOnly, approval_mode: AskForApproval::OnRequest, archived_at: None, @@ -163,11 +168,12 @@ impl ThreadMetadataBuilder { .clone() .unwrap_or_else(|| default_provider.to_string()), cwd: self.cwd.clone(), + cli_version: self.cli_version.clone().unwrap_or_default(), title: String::new(), sandbox_policy, approval_mode, tokens_used: 0, - has_user_event: false, + first_user_message: None, archived_at: self.archived_at.map(canonicalize_datetime), git_sha: self.git_sha.clone(), git_branch: self.git_branch.clone(), @@ -201,6 +207,9 @@ impl ThreadMetadata { if self.cwd != other.cwd { diffs.push("cwd"); } + if self.cli_version != other.cli_version { + diffs.push("cli_version"); + } if self.title != other.title { diffs.push("title"); } @@ -213,8 +222,8 @@ impl ThreadMetadata { if self.tokens_used != other.tokens_used { diffs.push("tokens_used"); } - if self.has_user_event != other.has_user_event { - diffs.push("has_user_event"); + if self.first_user_message != other.first_user_message { + diffs.push("first_user_message"); } if self.archived_at != other.archived_at { diffs.push("archived_at"); @@ -245,11 +254,12 @@ pub(crate) struct ThreadRow { source: String, model_provider: String, cwd: String, + cli_version: String, title: String, sandbox_policy: String, approval_mode: String, tokens_used: i64, - has_user_event: bool, + first_user_message: String, archived_at: Option, git_sha: Option, git_branch: Option, @@ -266,11 +276,12 @@ impl ThreadRow { source: row.try_get("source")?, model_provider: row.try_get("model_provider")?, cwd: row.try_get("cwd")?, + cli_version: row.try_get("cli_version")?, title: row.try_get("title")?, sandbox_policy: row.try_get("sandbox_policy")?, approval_mode: row.try_get("approval_mode")?, tokens_used: row.try_get("tokens_used")?, - has_user_event: row.try_get("has_user_event")?, + first_user_message: row.try_get("first_user_message")?, archived_at: row.try_get("archived_at")?, git_sha: row.try_get("git_sha")?, git_branch: row.try_get("git_branch")?, @@ -291,11 +302,12 @@ impl TryFrom for ThreadMetadata { source, model_provider, cwd, + cli_version, title, sandbox_policy, approval_mode, tokens_used, - has_user_event, + first_user_message, archived_at, git_sha, git_branch, @@ -309,11 +321,12 @@ impl TryFrom for ThreadMetadata { source, model_provider, cwd: PathBuf::from(cwd), + cli_version, title, sandbox_policy, approval_mode, tokens_used, - has_user_event, + first_user_message: (!first_user_message.is_empty()).then_some(first_user_message), archived_at: archived_at.map(epoch_seconds_to_datetime).transpose()?, git_sha, git_branch, diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 384fc57cad..33ee7c844b 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -177,11 +177,12 @@ SELECT source, model_provider, cwd, + cli_version, title, sandbox_policy, approval_mode, tokens_used, - has_user_event, + first_user_message, archived_at, git_sha, git_branch, @@ -295,11 +296,12 @@ SELECT source, model_provider, cwd, + cli_version, title, sandbox_policy, approval_mode, tokens_used, - has_user_event, + first_user_message, archived_at, git_sha, git_branch, @@ -449,17 +451,18 @@ INSERT INTO threads ( source, model_provider, cwd, + cli_version, title, sandbox_policy, approval_mode, tokens_used, - has_user_event, + first_user_message, archived, archived_at, git_sha, git_branch, git_origin_url -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET rollout_path = excluded.rollout_path, created_at = excluded.created_at, @@ -467,11 +470,12 @@ ON CONFLICT(id) DO UPDATE SET source = excluded.source, model_provider = excluded.model_provider, cwd = excluded.cwd, + cli_version = excluded.cli_version, title = excluded.title, sandbox_policy = excluded.sandbox_policy, approval_mode = excluded.approval_mode, tokens_used = excluded.tokens_used, - has_user_event = excluded.has_user_event, + first_user_message = excluded.first_user_message, archived = excluded.archived, archived_at = excluded.archived_at, git_sha = excluded.git_sha, @@ -486,11 +490,12 @@ ON CONFLICT(id) DO UPDATE SET .bind(metadata.source.as_str()) .bind(metadata.model_provider.as_str()) .bind(metadata.cwd.display().to_string()) + .bind(metadata.cli_version.as_str()) .bind(metadata.title.as_str()) .bind(metadata.sandbox_policy.as_str()) .bind(metadata.approval_mode.as_str()) .bind(metadata.tokens_used) - .bind(metadata.has_user_event) + .bind(metadata.first_user_message.as_deref().unwrap_or_default()) .bind(metadata.archived_at.is_some()) .bind(metadata.archived_at.map(datetime_to_epoch_seconds)) .bind(metadata.git_sha.as_deref()) @@ -900,7 +905,7 @@ fn push_thread_filters<'a>( } else { builder.push(" AND archived = 0"); } - builder.push(" AND has_user_event = 1"); + builder.push(" AND first_user_message <> ''"); if !allowed_sources.is_empty() { builder.push(" AND source IN ("); let mut separated = builder.separated(", "); @@ -1391,11 +1396,12 @@ mod tests { source: "cli".to_string(), model_provider: "test-provider".to_string(), cwd, + cli_version: "0.0.0".to_string(), title: String::new(), sandbox_policy: crate::extract::enum_to_string(&SandboxPolicy::ReadOnly), approval_mode: crate::extract::enum_to_string(&AskForApproval::OnRequest), tokens_used: 0, - has_user_event: true, + first_user_message: Some("hello".to_string()), archived_at: None, git_sha: None, git_branch: None, diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index e80c4ad793..f35acc07ff 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -14,7 +14,6 @@ use codex_core::ThreadSortKey; use codex_core::ThreadsPage; use codex_core::find_thread_names_by_ids; use codex_core::path_utils; -use codex_protocol::items::TurnItem; use color_eyre::eyre::Result; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; @@ -37,8 +36,6 @@ use crate::tui::FrameRequester; use crate::tui::Tui; use crate::tui::TuiEvent; use codex_protocol::ThreadId; -use codex_protocol::models::ResponseItem; -use codex_protocol::protocol::SessionMetaLine; const PAGE_SIZE: usize = 25; const LOAD_NEAR_THRESHOLD: usize = 5; @@ -766,49 +763,33 @@ fn rows_from_items(items: Vec) -> Vec { } fn head_to_row(item: &ThreadItem) -> Row { - let created_at = item - .created_at - .as_deref() - .and_then(parse_timestamp_str) - .or_else(|| item.head.first().and_then(extract_timestamp)); + let created_at = item.created_at.as_deref().and_then(parse_timestamp_str); let updated_at = item .updated_at .as_deref() .and_then(parse_timestamp_str) .or(created_at); - let (cwd, git_branch, thread_id) = extract_session_meta_from_head(&item.head); - let preview = preview_from_head(&item.head) - .map(|s| s.trim().to_string()) + let preview = item + .first_user_message + .as_deref() + .map(str::trim) .filter(|s| !s.is_empty()) + .map(str::to_string) .unwrap_or_else(|| String::from("(no message yet)")); Row { path: item.path.clone(), preview, - thread_id, + thread_id: item.thread_id, thread_name: None, created_at, updated_at, - cwd, - git_branch, + cwd: item.cwd.clone(), + git_branch: item.git_branch.clone(), } } -fn extract_session_meta_from_head( - head: &[serde_json::Value], -) -> (Option, Option, Option) { - for value in head { - if let Ok(meta_line) = serde_json::from_value::(value.clone()) { - let cwd = Some(meta_line.meta.cwd); - let git_branch = meta_line.git.and_then(|git| git.branch); - let thread_id = Some(meta_line.meta.id); - return (cwd, git_branch, thread_id); - } - } - (None, None, None) -} - fn paths_match(a: &Path, b: &Path) -> bool { if let (Ok(ca), Ok(cb)) = ( path_utils::normalize_for_path_comparison(a), @@ -825,23 +806,6 @@ fn parse_timestamp_str(ts: &str) -> Option> { .ok() } -fn extract_timestamp(value: &serde_json::Value) -> Option> { - value - .get("timestamp") - .and_then(|v| v.as_str()) - .and_then(|t| chrono::DateTime::parse_from_rfc3339(t).ok()) - .map(|dt| dt.with_timezone(&Utc)) -} - -fn preview_from_head(head: &[serde_json::Value]) -> Option { - head.iter() - .filter_map(|value| serde_json::from_value::(value.clone()).ok()) - .find_map(|item| match codex_core::parse_turn_item(&item) { - Some(TurnItem::UserMessage(user)) => Some(user.message()), - _ => None, - }) -} - fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> { // Render full-screen overlay let height = tui.terminal.size()?.height; @@ -1200,24 +1164,18 @@ mod tests { use std::sync::Arc; use std::sync::Mutex; - fn head_with_ts_and_user_text(ts: &str, texts: &[&str]) -> Vec { - vec![ - json!({ "timestamp": ts }), - json!({ - "type": "message", - "role": "user", - "content": texts - .iter() - .map(|t| json!({ "type": "input_text", "text": *t })) - .collect::>() - }), - ] - } - fn make_item(path: &str, ts: &str, preview: &str) -> ThreadItem { ThreadItem { path: PathBuf::from(path), - head: head_with_ts_and_user_text(ts, &[preview]), + thread_id: None, + first_user_message: Some(preview.to_string()), + cwd: None, + git_branch: None, + git_sha: None, + git_origin_url: None, + source: None, + model_provider: None, + cli_version: None, created_at: Some(ts.to_string()), updated_at: Some(ts.to_string()), } @@ -1243,39 +1201,23 @@ mod tests { } #[test] - fn preview_uses_first_message_input_text() { - let head = vec![ - json!({ "timestamp": "2025-01-01T00:00:00Z" }), - json!({ - "type": "message", - "role": "user", - "content": [ - { "type": "input_text", "text": "# AGENTS.md instructions for project\n\n\nhi\n" }, - ] - }), - json!({ - "type": "message", - "role": "user", - "content": [ - { "type": "input_text", "text": "..." }, - ] - }), - json!({ - "type": "message", - "role": "user", - "content": [ - { "type": "input_text", "text": "real question" }, - { "type": "input_image", "image_url": "ignored" } - ] - }), - json!({ - "type": "message", - "role": "user", - "content": [ { "type": "input_text", "text": "later text" } ] - }), - ]; - let preview = preview_from_head(&head); - assert_eq!(preview.as_deref(), Some("real question")); + fn head_to_row_uses_first_user_message() { + let item = ThreadItem { + path: PathBuf::from("/tmp/a.jsonl"), + thread_id: None, + first_user_message: Some("real question".to_string()), + cwd: None, + git_branch: None, + git_sha: None, + git_origin_url: None, + source: None, + model_provider: None, + cli_version: None, + created_at: Some("2025-01-01T00:00:00Z".into()), + updated_at: Some("2025-01-01T00:00:00Z".into()), + }; + let row = head_to_row(&item); + assert_eq!(row.preview, "real question"); } #[test] @@ -1283,13 +1225,29 @@ mod tests { // Construct two items with different timestamps and real user text. let a = ThreadItem { path: PathBuf::from("/tmp/a.jsonl"), - head: head_with_ts_and_user_text("2025-01-01T00:00:00Z", &["A"]), + thread_id: None, + first_user_message: Some("A".to_string()), + cwd: None, + git_branch: None, + git_sha: None, + git_origin_url: None, + source: None, + model_provider: None, + cli_version: None, created_at: Some("2025-01-01T00:00:00Z".into()), updated_at: Some("2025-01-01T00:00:00Z".into()), }; let b = ThreadItem { path: PathBuf::from("/tmp/b.jsonl"), - head: head_with_ts_and_user_text("2025-01-02T00:00:00Z", &["B"]), + thread_id: None, + first_user_message: Some("B".to_string()), + cwd: None, + git_branch: None, + git_sha: None, + git_origin_url: None, + source: None, + model_provider: None, + cli_version: None, created_at: Some("2025-01-02T00:00:00Z".into()), updated_at: Some("2025-01-02T00:00:00Z".into()), }; @@ -1302,10 +1260,17 @@ mod tests { #[test] fn row_uses_tail_timestamp_for_updated_at() { - let head = head_with_ts_and_user_text("2025-01-01T00:00:00Z", &["Hello"]); let item = ThreadItem { path: PathBuf::from("/tmp/a.jsonl"), - head, + thread_id: None, + first_user_message: Some("Hello".to_string()), + cwd: None, + git_branch: None, + git_sha: None, + git_origin_url: None, + source: None, + model_provider: None, + cli_version: None, created_at: Some("2025-01-01T00:00:00Z".into()), updated_at: Some("2025-01-01T01:00:00Z".into()), }; From 3b54fd733601cbc8bfc789cbcf82f7bd9dfa833b Mon Sep 17 00:00:00 2001 From: gt-oai Date: Thu, 5 Feb 2026 16:49:35 +0000 Subject: [PATCH 66/90] Add hooks implementation and wire up to `notify` (#9691) This introduces a `Hooks` service. It registers hooks from config and dispatches hook events at runtime. N.B. The hook config is not wired up to this yet. But for legacy reasons, we wire up `notify` from config and power it using hooks now. Nothing about the `notify` interface has changed. I'd start by reviewing `hooks/types.rs` Some things to note: - hook names subject to change - no hook result yet - stopping semantics yet to be introduced - additional hooks yet to be introduced --- codex-rs/core/src/codex.rs | 38 ++- codex-rs/core/src/hooks/mod.rs | 8 + codex-rs/core/src/hooks/registry.rs | 315 +++++++++++++++++++ codex-rs/core/src/hooks/types.rs | 127 ++++++++ codex-rs/core/src/hooks/user_notification.rs | 132 ++++++++ codex-rs/core/src/lib.rs | 2 +- codex-rs/core/src/state/service.rs | 4 +- codex-rs/core/src/user_notification.rs | 87 ----- 8 files changed, 608 insertions(+), 105 deletions(-) create mode 100644 codex-rs/core/src/hooks/mod.rs create mode 100644 codex-rs/core/src/hooks/registry.rs create mode 100644 codex-rs/core/src/hooks/types.rs create mode 100644 codex-rs/core/src/hooks/user_notification.rs delete mode 100644 codex-rs/core/src/user_notification.rs diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index dc7d5902d6..95c13ec9df 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -24,6 +24,9 @@ use crate::features::FEATURES; use crate::features::Feature; use crate::features::Features; use crate::features::maybe_push_unstable_features_warning; +use crate::hooks::HookEvent; +use crate::hooks::HookEventAfterAgent; +use crate::hooks::Hooks; use crate::models_manager::manager::ModelsManager; use crate::parse_command::parse_command; use crate::parse_turn_item; @@ -35,7 +38,6 @@ use crate::stream_events_utils::last_assistant_message_from_item; use crate::terminal; use crate::truncate::TruncationPolicy; use crate::turn_metadata::build_turn_metadata_header; -use crate::user_notification::UserNotifier; use crate::util::error_or_panic; use async_channel::Receiver; use async_channel::Sender; @@ -201,7 +203,6 @@ use crate::tools::spec::ToolsConfig; use crate::tools::spec::ToolsConfigParams; use crate::turn_diff_tracker::TurnDiffTracker; use crate::unified_exec::UnifiedExecProcessManager; -use crate::user_notification::UserNotification; use crate::util::backoff; use crate::windows_sandbox::WindowsSandboxLevelExt; use codex_async_utils::OrCancelExt; @@ -1015,7 +1016,7 @@ impl Session { Arc::clone(&config), Arc::clone(&auth_manager), ), - notifier: UserNotifier::new(config.notify.clone()), + hooks: Hooks::new(config.as_ref()), rollout: Mutex::new(rollout_recorder), user_shell: Arc::new(default_shell), show_raw_agent_reasoning: config.show_raw_agent_reasoning, @@ -2450,8 +2451,8 @@ impl Session { } } - pub(crate) fn notifier(&self) -> &UserNotifier { - &self.services.notifier + pub(crate) fn hooks(&self) -> &Hooks { + &self.services.hooks } pub(crate) fn user_shell(&self) -> Arc { @@ -3776,14 +3777,21 @@ pub(crate) async fn run_turn( if !needs_follow_up { last_agent_message = sampling_request_last_agent_message; - sess.notifier() - .notify(&UserNotification::AgentTurnComplete { - thread_id: sess.conversation_id.to_string(), - turn_id: turn_context.sub_id.clone(), - cwd: turn_context.cwd.display().to_string(), - input_messages: sampling_request_input_messages, - last_assistant_message: last_agent_message.clone(), - }); + sess.hooks() + .dispatch(crate::hooks::HookPayload { + session_id: sess.conversation_id, + cwd: turn_context.cwd.clone(), + triggered_at: chrono::Utc::now(), + hook_event: HookEvent::AfterAgent { + event: HookEventAfterAgent { + thread_id: sess.conversation_id, + turn_id: turn_context.sub_id.clone(), + input_messages: sampling_request_input_messages, + last_assistant_message: last_agent_message.clone(), + }, + }, + }) + .await; break; } continue; @@ -5724,7 +5732,7 @@ mod tests { Arc::clone(&config), Arc::clone(&auth_manager), ), - notifier: UserNotifier::new(None), + hooks: Hooks::new(&config), rollout: Mutex::new(None), user_shell: Arc::new(default_user_shell()), show_raw_agent_reasoning: config.show_raw_agent_reasoning, @@ -5854,7 +5862,7 @@ mod tests { Arc::clone(&config), Arc::clone(&auth_manager), ), - notifier: UserNotifier::new(None), + hooks: Hooks::new(&config), rollout: Mutex::new(None), user_shell: Arc::new(default_user_shell()), show_raw_agent_reasoning: config.show_raw_agent_reasoning, diff --git a/codex-rs/core/src/hooks/mod.rs b/codex-rs/core/src/hooks/mod.rs new file mode 100644 index 0000000000..2c0612825d --- /dev/null +++ b/codex-rs/core/src/hooks/mod.rs @@ -0,0 +1,8 @@ +mod registry; +mod types; +mod user_notification; + +pub(crate) use registry::Hooks; +pub(crate) use types::HookEvent; +pub(crate) use types::HookEventAfterAgent; +pub(crate) use types::HookPayload; diff --git a/codex-rs/core/src/hooks/registry.rs b/codex-rs/core/src/hooks/registry.rs new file mode 100644 index 0000000000..6bccee85c6 --- /dev/null +++ b/codex-rs/core/src/hooks/registry.rs @@ -0,0 +1,315 @@ +use tokio::process::Command; + +use super::types::Hook; +use super::types::HookEvent; +use super::types::HookOutcome; +use super::types::HookPayload; +use super::user_notification::notify_hook; +use crate::config::Config; + +#[derive(Default, Clone)] +pub(crate) struct Hooks { + after_agent: Vec, +} + +fn get_notify_hook(config: &Config) -> Option { + config + .notify + .as_ref() + .filter(|argv| !argv.is_empty() && !argv[0].is_empty()) + .map(|argv| notify_hook(argv.clone())) +} + +// Hooks are arbitrary, user-specified functions that are deterministically +// executed after specific events in the Codex lifecycle. +impl Hooks { + // new creates a new Hooks instance from config. + // For legacy compatibility, if config.notify is set, it will be added to + // the after_agent hooks. + pub(crate) fn new(config: &Config) -> Self { + let after_agent = get_notify_hook(config).into_iter().collect(); + Self { after_agent } + } + + fn hooks_for_event(&self, hook_event: &HookEvent) -> &[Hook] { + match hook_event { + HookEvent::AfterAgent { .. } => &self.after_agent, + } + } + + pub(crate) async fn dispatch(&self, hook_payload: HookPayload) { + // TODO(gt): support interrupting program execution by returning a result here. + for hook in self.hooks_for_event(&hook_payload.hook_event) { + let outcome = hook.execute(&hook_payload).await; + if matches!(outcome, HookOutcome::Stop) { + break; + } + } + } +} + +pub(super) fn command_from_argv(argv: &[String]) -> Option { + let (program, args) = argv.split_first()?; + if program.is_empty() { + return None; + } + let mut command = Command::new(program); + command.args(args); + Some(command) +} + +#[cfg(test)] +mod tests { + use std::fs; + use std::path::PathBuf; + use std::process::Stdio; + use std::sync::Arc; + use std::sync::atomic::AtomicUsize; + use std::sync::atomic::Ordering; + use std::time::Duration; + + use anyhow::Result; + use chrono::TimeZone; + use chrono::Utc; + use codex_protocol::ThreadId; + use pretty_assertions::assert_eq; + use serde_json::to_string; + use tempfile::tempdir; + use tokio::time::timeout; + + use crate::config::test_config; + + use super::super::types::Hook; + use super::super::types::HookEvent; + use super::super::types::HookEventAfterAgent; + use super::super::types::HookOutcome; + use super::super::types::HookPayload; + use super::Hooks; + use super::command_from_argv; + use super::get_notify_hook; + + const CWD: &str = "/tmp"; + const INPUT_MESSAGE: &str = "hello"; + + fn hook_payload(label: &str) -> HookPayload { + HookPayload { + session_id: ThreadId::new(), + cwd: PathBuf::from(CWD), + triggered_at: Utc + .with_ymd_and_hms(2025, 1, 1, 0, 0, 0) + .single() + .expect("valid timestamp"), + hook_event: HookEvent::AfterAgent { + event: HookEventAfterAgent { + thread_id: ThreadId::new(), + turn_id: format!("turn-{label}"), + input_messages: vec![INPUT_MESSAGE.to_string()], + last_assistant_message: Some("hi".to_string()), + }, + }, + } + } + + fn counting_hook(calls: &Arc, outcome: HookOutcome) -> Hook { + let calls = Arc::clone(calls); + Hook { + func: Arc::new(move |_| { + let calls = Arc::clone(&calls); + Box::pin(async move { + calls.fetch_add(1, Ordering::SeqCst); + outcome + }) + }), + } + } + + fn hooks_for_after_agent(hooks: Vec) -> Hooks { + Hooks { after_agent: hooks } + } + + #[test] + fn command_from_argv_returns_none_for_empty_args() { + assert!(command_from_argv(&[]).is_none()); + assert!(command_from_argv(&["".to_string()]).is_none()); + } + + #[tokio::test] + async fn command_from_argv_builds_command() -> Result<()> { + let argv = if cfg!(windows) { + vec![ + "cmd".to_string(), + "/C".to_string(), + "echo hello world".to_string(), + ] + } else { + vec!["echo".to_string(), "hello".to_string(), "world".to_string()] + }; + let mut command = command_from_argv(&argv).ok_or_else(|| anyhow::anyhow!("command"))?; + let output = command.stdout(Stdio::piped()).output().await?; + + let stdout = String::from_utf8_lossy(&output.stdout); + let trimmed = stdout.trim_end_matches(['\r', '\n']); + assert_eq!(trimmed, "hello world"); + Ok(()) + } + + #[test] + fn get_notify_hook_requires_program_name() { + let mut config = test_config(); + + config.notify = Some(vec![]); + assert!(get_notify_hook(&config).is_none()); + + config.notify = Some(vec!["".to_string()]); + assert!(get_notify_hook(&config).is_none()); + + config.notify = Some(vec!["notify-send".to_string()]); + assert!(get_notify_hook(&config).is_some()); + } + + #[tokio::test] + async fn dispatch_executes_hook() { + let calls = Arc::new(AtomicUsize::new(0)); + let hooks = hooks_for_after_agent(vec![counting_hook(&calls, HookOutcome::Continue)]); + + hooks.dispatch(hook_payload("1")).await; + assert_eq!(calls.load(Ordering::SeqCst), 1); + } + + #[tokio::test] + async fn default_hook_is_noop_and_continues() { + let payload = hook_payload("d"); + let outcome = Hook::default().execute(&payload).await; + assert_eq!(outcome, HookOutcome::Continue); + } + + #[tokio::test] + async fn dispatch_executes_multiple_hooks_for_same_event() { + let calls = Arc::new(AtomicUsize::new(0)); + let hooks = hooks_for_after_agent(vec![ + counting_hook(&calls, HookOutcome::Continue), + counting_hook(&calls, HookOutcome::Continue), + ]); + + hooks.dispatch(hook_payload("2")).await; + assert_eq!(calls.load(Ordering::SeqCst), 2); + } + + #[tokio::test] + async fn dispatch_stops_when_hook_returns_stop() { + let calls = Arc::new(AtomicUsize::new(0)); + let hooks = hooks_for_after_agent(vec![ + counting_hook(&calls, HookOutcome::Stop), + counting_hook(&calls, HookOutcome::Continue), + ]); + + hooks.dispatch(hook_payload("3")).await; + assert_eq!(calls.load(Ordering::SeqCst), 1); + } + + #[cfg(not(windows))] + #[tokio::test] + async fn hook_executes_program_with_payload_argument_unix() -> Result<()> { + let temp_dir = tempdir()?; + let payload_path = temp_dir.path().join("payload.json"); + let payload_path_arg = payload_path.to_string_lossy().into_owned(); + let hook = Hook { + func: Arc::new(move |payload: &HookPayload| { + let payload_path_arg = payload_path_arg.clone(); + Box::pin(async move { + let json = to_string(payload).expect("serialize hook payload"); + let mut command = command_from_argv(&[ + "/bin/sh".to_string(), + "-c".to_string(), + "printf '%s' \"$2\" > \"$1\"".to_string(), + "sh".to_string(), + payload_path_arg, + json, + ]) + .expect("build command"); + command.status().await.expect("run hook command"); + HookOutcome::Continue + }) + }), + }; + + let payload = hook_payload("4"); + let expected = to_string(&payload)?; + + let hooks = hooks_for_after_agent(vec![hook]); + hooks.dispatch(payload).await; + + let contents = timeout(Duration::from_secs(2), async { + loop { + if let Ok(contents) = fs::read_to_string(&payload_path) + && !contents.is_empty() + { + return contents; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await?; + + assert_eq!(contents, expected); + Ok(()) + } + + #[cfg(windows)] + #[tokio::test] + async fn hook_executes_program_with_payload_argument_windows() -> Result<()> { + let temp_dir = tempdir()?; + let payload_path = temp_dir.path().join("payload.json"); + let payload_path_arg = payload_path.to_string_lossy().into_owned(); + let script_path = temp_dir.path().join("write_payload.ps1"); + fs::write(&script_path, "[IO.File]::WriteAllText($args[0], $args[1])")?; + let script_path_arg = script_path.to_string_lossy().into_owned(); + let hook = Hook { + func: Arc::new(move |payload: &HookPayload| { + let payload_path_arg = payload_path_arg.clone(); + let script_path_arg = script_path_arg.clone(); + Box::pin(async move { + let json = to_string(payload).expect("serialize hook payload"); + let powershell = crate::powershell::try_find_powershell_executable_blocking() + .map(|path| path.to_string_lossy().into_owned()) + .unwrap_or_else(|| "powershell.exe".to_string()); + let mut command = command_from_argv(&[ + powershell, + "-NoLogo".to_string(), + "-NoProfile".to_string(), + "-ExecutionPolicy".to_string(), + "Bypass".to_string(), + "-File".to_string(), + script_path_arg, + payload_path_arg, + json, + ]) + .expect("build command"); + command.status().await.expect("run hook command"); + HookOutcome::Continue + }) + }), + }; + + let payload = hook_payload("4"); + let expected = to_string(&payload)?; + + let hooks = hooks_for_after_agent(vec![hook]); + hooks.dispatch(payload).await; + + let contents = timeout(Duration::from_secs(2), async { + loop { + if let Ok(contents) = fs::read_to_string(&payload_path) + && !contents.is_empty() + { + return contents; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + }) + .await?; + + assert_eq!(contents, expected); + Ok(()) + } +} diff --git a/codex-rs/core/src/hooks/types.rs b/codex-rs/core/src/hooks/types.rs new file mode 100644 index 0000000000..3b22d031b6 --- /dev/null +++ b/codex-rs/core/src/hooks/types.rs @@ -0,0 +1,127 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use chrono::DateTime; +use chrono::SecondsFormat; +use chrono::Utc; +use codex_protocol::ThreadId; +use futures::future::BoxFuture; +use serde::Serialize; +use serde::Serializer; + +pub(crate) type HookFn = + Arc Fn(&'a HookPayload) -> BoxFuture<'a, HookOutcome> + Send + Sync>; + +#[derive(Clone)] +pub(crate) struct Hook { + pub(crate) func: HookFn, +} + +impl Default for Hook { + fn default() -> Self { + Self { + func: Arc::new(|_| Box::pin(async { HookOutcome::Continue })), + } + } +} + +impl Hook { + pub(super) async fn execute(&self, payload: &HookPayload) -> HookOutcome { + (self.func)(payload).await + } +} + +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "snake_case")] +pub(crate) struct HookPayload { + pub(crate) session_id: ThreadId, + pub(crate) cwd: PathBuf, + #[serde(serialize_with = "serialize_triggered_at")] + pub(crate) triggered_at: DateTime, + pub(crate) hook_event: HookEvent, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "snake_case")] +pub(crate) struct HookEventAfterAgent { + pub thread_id: ThreadId, + pub turn_id: String, + pub input_messages: Vec, + pub last_assistant_message: Option, +} + +fn serialize_triggered_at(value: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&value.to_rfc3339_opts(SecondsFormat::Secs, true)) +} + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "event_type", rename_all = "snake_case")] +pub(crate) enum HookEvent { + AfterAgent { + #[serde(flatten)] + event: HookEventAfterAgent, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum HookOutcome { + Continue, + #[allow(dead_code)] + Stop, +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use chrono::TimeZone; + use chrono::Utc; + use codex_protocol::ThreadId; + use pretty_assertions::assert_eq; + use serde_json::json; + + use super::HookEvent; + use super::HookEventAfterAgent; + use super::HookPayload; + + #[test] + fn hook_payload_serializes_stable_wire_shape() { + let session_id = ThreadId::new(); + let thread_id = ThreadId::new(); + let payload = HookPayload { + session_id, + cwd: PathBuf::from("tmp"), + triggered_at: Utc + .with_ymd_and_hms(2025, 1, 1, 0, 0, 0) + .single() + .expect("valid timestamp"), + hook_event: HookEvent::AfterAgent { + event: HookEventAfterAgent { + thread_id, + turn_id: "turn-1".to_string(), + input_messages: vec!["hello".to_string()], + last_assistant_message: Some("hi".to_string()), + }, + }, + }; + + let actual = serde_json::to_value(payload).expect("serialize hook payload"); + let expected = json!({ + "session_id": session_id.to_string(), + "cwd": "tmp", + "triggered_at": "2025-01-01T00:00:00Z", + "hook_event": { + "event_type": "after_agent", + "thread_id": thread_id.to_string(), + "turn_id": "turn-1", + "input_messages": ["hello"], + "last_assistant_message": "hi", + }, + }); + + assert_eq!(actual, expected); + } +} diff --git a/codex-rs/core/src/hooks/user_notification.rs b/codex-rs/core/src/hooks/user_notification.rs new file mode 100644 index 0000000000..de1317f9c3 --- /dev/null +++ b/codex-rs/core/src/hooks/user_notification.rs @@ -0,0 +1,132 @@ +use std::sync::Arc; + +use serde::Serialize; +use std::path::Path; +use std::process::Stdio; + +use super::registry::command_from_argv; +use super::types::Hook; +use super::types::HookEvent; +use super::types::HookOutcome; +use super::types::HookPayload; + +/// Legacy notify payload appended as the final argv argument for backward compatibility. +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "type", rename_all = "kebab-case")] +enum UserNotification { + #[serde(rename_all = "kebab-case")] + AgentTurnComplete { + thread_id: String, + turn_id: String, + cwd: String, + + /// Messages that the user sent to the agent to initiate the turn. + input_messages: Vec, + + /// The last message sent by the assistant in the turn. + last_assistant_message: Option, + }, +} + +pub(super) fn legacy_notify_json( + hook_event: &HookEvent, + cwd: &Path, +) -> Result { + serde_json::to_string(&match hook_event { + HookEvent::AfterAgent { event } => UserNotification::AgentTurnComplete { + thread_id: event.thread_id.to_string(), + turn_id: event.turn_id.clone(), + cwd: cwd.display().to_string(), + input_messages: event.input_messages.clone(), + last_assistant_message: event.last_assistant_message.clone(), + }, + }) +} + +pub(super) fn notify_hook(argv: Vec) -> Hook { + let argv = Arc::new(argv); + Hook { + func: Arc::new(move |payload: &HookPayload| { + let argv = Arc::clone(&argv); + Box::pin(async move { + let mut command = match command_from_argv(&argv) { + Some(command) => command, + None => return HookOutcome::Continue, + }; + if let Ok(notify_payload) = legacy_notify_json(&payload.hook_event, &payload.cwd) { + command.arg(notify_payload); + } + + // Backwards-compat: match legacy notify behavior (argv + JSON arg, fire-and-forget). + command + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + + let _ = command.spawn(); + HookOutcome::Continue + }) + }), + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + use anyhow::Result; + use codex_protocol::ThreadId; + use pretty_assertions::assert_eq; + use serde_json::Value; + use serde_json::json; + + fn expected_notification_json() -> Value { + json!({ + "type": "agent-turn-complete", + "thread-id": "b5f6c1c2-1111-2222-3333-444455556666", + "turn-id": "12345", + "cwd": "/Users/example/project", + "input-messages": ["Rename `foo` to `bar` and update the callsites."], + "last-assistant-message": "Rename complete and verified `cargo build` succeeds.", + }) + } + + #[test] + fn test_user_notification() -> Result<()> { + let notification = UserNotification::AgentTurnComplete { + thread_id: "b5f6c1c2-1111-2222-3333-444455556666".to_string(), + turn_id: "12345".to_string(), + cwd: "/Users/example/project".to_string(), + input_messages: vec!["Rename `foo` to `bar` and update the callsites.".to_string()], + last_assistant_message: Some( + "Rename complete and verified `cargo build` succeeds.".to_string(), + ), + }; + let serialized = serde_json::to_string(¬ification)?; + let actual: Value = serde_json::from_str(&serialized)?; + assert_eq!(actual, expected_notification_json()); + Ok(()) + } + + #[test] + fn legacy_notify_json_matches_historical_wire_shape() -> Result<()> { + let hook_event = HookEvent::AfterAgent { + event: super::super::types::HookEventAfterAgent { + thread_id: ThreadId::from_string("b5f6c1c2-1111-2222-3333-444455556666") + .expect("valid thread id"), + turn_id: "12345".to_string(), + input_messages: vec!["Rename `foo` to `bar` and update the callsites.".to_string()], + last_assistant_message: Some( + "Rename complete and verified `cargo build` succeeds.".to_string(), + ), + }, + }; + + let serialized = legacy_notify_json(&hook_event, Path::new("/Users/example/project"))?; + let actual: Value = serde_json::from_str(&serialized)?; + assert_eq!(actual, expected_notification_json()); + + Ok(()) + } +} diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 7b69c4370a..ed7fc6e3e3 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -35,6 +35,7 @@ pub mod features; mod file_watcher; mod flags; pub mod git_info; +pub mod hooks; pub mod instructions; pub mod landlock; pub mod mcp; @@ -125,7 +126,6 @@ pub use rollout::session_index::find_thread_names_by_ids; mod function_tool; mod state; mod tasks; -mod user_notification; mod user_shell_command; pub mod util; diff --git a/codex-rs/core/src/state/service.rs b/codex-rs/core/src/state/service.rs index 0e9c038038..d9f57cb5f5 100644 --- a/codex-rs/core/src/state/service.rs +++ b/codex-rs/core/src/state/service.rs @@ -7,13 +7,13 @@ use crate::analytics_client::AnalyticsEventsClient; use crate::client::ModelClient; use crate::exec_policy::ExecPolicyManager; use crate::file_watcher::FileWatcher; +use crate::hooks::Hooks; use crate::mcp_connection_manager::McpConnectionManager; use crate::models_manager::manager::ModelsManager; use crate::skills::SkillsManager; use crate::state_db::StateDbHandle; use crate::tools::sandboxing::ApprovalStore; use crate::unified_exec::UnifiedExecProcessManager; -use crate::user_notification::UserNotifier; use codex_otel::OtelManager; use tokio::sync::Mutex; use tokio::sync::RwLock; @@ -24,7 +24,7 @@ pub(crate) struct SessionServices { pub(crate) mcp_startup_cancellation_token: Mutex, pub(crate) unified_exec_manager: UnifiedExecProcessManager, pub(crate) analytics_events_client: AnalyticsEventsClient, - pub(crate) notifier: UserNotifier, + pub(crate) hooks: Hooks, pub(crate) rollout: Mutex>, pub(crate) user_shell: Arc, pub(crate) show_raw_agent_reasoning: bool, diff --git a/codex-rs/core/src/user_notification.rs b/codex-rs/core/src/user_notification.rs deleted file mode 100644 index 7bbd1d9564..0000000000 --- a/codex-rs/core/src/user_notification.rs +++ /dev/null @@ -1,87 +0,0 @@ -use serde::Serialize; -use tracing::error; -use tracing::warn; - -#[derive(Debug, Default)] -pub(crate) struct UserNotifier { - notify_command: Option>, -} - -impl UserNotifier { - pub(crate) fn notify(&self, notification: &UserNotification) { - if let Some(notify_command) = &self.notify_command - && !notify_command.is_empty() - { - self.invoke_notify(notify_command, notification) - } - } - - fn invoke_notify(&self, notify_command: &[String], notification: &UserNotification) { - let Ok(json) = serde_json::to_string(¬ification) else { - error!("failed to serialise notification payload"); - return; - }; - - let mut command = std::process::Command::new(¬ify_command[0]); - if notify_command.len() > 1 { - command.args(¬ify_command[1..]); - } - command.arg(json); - - // Fire-and-forget – we do not wait for completion. - if let Err(e) = command.spawn() { - warn!("failed to spawn notifier '{}': {e}", notify_command[0]); - } - } - - pub(crate) fn new(notify: Option>) -> Self { - Self { - notify_command: notify, - } - } -} - -/// User can configure a program that will receive notifications. Each -/// notification is serialized as JSON and passed as an argument to the -/// program. -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -pub(crate) enum UserNotification { - #[serde(rename_all = "kebab-case")] - AgentTurnComplete { - thread_id: String, - turn_id: String, - cwd: String, - - /// Messages that the user sent to the agent to initiate the turn. - input_messages: Vec, - - /// The last message sent by the assistant in the turn. - last_assistant_message: Option, - }, -} - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - - #[test] - fn test_user_notification() -> Result<()> { - let notification = UserNotification::AgentTurnComplete { - thread_id: "b5f6c1c2-1111-2222-3333-444455556666".to_string(), - turn_id: "12345".to_string(), - cwd: "/Users/example/project".to_string(), - input_messages: vec!["Rename `foo` to `bar` and update the callsites.".to_string()], - last_assistant_message: Some( - "Rename complete and verified `cargo build` succeeds.".to_string(), - ), - }; - let serialized = serde_json::to_string(¬ification)?; - assert_eq!( - serialized, - r#"{"type":"agent-turn-complete","thread-id":"b5f6c1c2-1111-2222-3333-444455556666","turn-id":"12345","cwd":"/Users/example/project","input-messages":["Rename `foo` to `bar` and update the callsites."],"last-assistant-message":"Rename complete and verified `cargo build` succeeds."}"# - ); - Ok(()) - } -} From b0e5a6305b83ff20779d4820353d36b80cbd5896 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Thu, 5 Feb 2026 13:50:21 -0300 Subject: [PATCH 67/90] feat(tui): add /statusline command for interactive status line configuration (#10546) ## Summary - Adds a new `/statusline` command to configure TUI footer status line - Introduces reusable `MultiSelectPicker` component with keyboard navigation, optional ordering and toggle support - Implement status line setup modal that persist configuration to config.toml ## Status Line Items The following items can be displayed in the status line: - **Model**: Current model name (with optional reasoning level) - **Context**: Remaining/used context window percentage - **Rate Limits**: 5-day and weekly usage limits - **Git**: Current branch (with optimized lookups) - **Tokens**: Used tokens, input/output token counts - **Session**: Session ID (full or shortened prefix) - **Paths**: Current directory, project root - **Version**: Codex version ## Features - Live preview while configuring status line items - Fuzzy search filtering in the picker - Intelligent truncation when items don't fit - Items gracefully omit when data is unavailable - Configuration persists to `config.toml` - Validates and warns about invalid status line items ## Test plan - [x] Run `/statusline` and verify picker UI appears - [x] Toggle items on/off and verify live preview updates - [x] Confirm selection persists after restart - [x] Verify truncation behavior with many items selected - [x] Test git branch detection in and out of git repos --------- Co-authored-by: Josh McKinney --- codex-rs/core/config.schema.json | 8 + codex-rs/core/src/config/edit.rs | 18 + codex-rs/core/src/config/mod.rs | 9 + codex-rs/core/src/config/types.rs | 6 + codex-rs/tui/src/app.rs | 72 ++ codex-rs/tui/src/app_event.rs | 13 + codex-rs/tui/src/bottom_pane/chat_composer.rs | 145 +++- codex-rs/tui/src/bottom_pane/footer.rs | 375 ++++++++- codex-rs/tui/src/bottom_pane/mod.rs | 15 + .../src/bottom_pane/multi_select_picker.rs | 795 ++++++++++++++++++ .../src/bottom_pane/selection_popup_common.rs | 32 +- ...er_status_line_disabled_context_right.snap | 5 + ...footer_status_line_enabled_mode_right.snap | 5 + ...ter_status_line_enabled_no_mode_right.snap | 5 + ..._footer_status_line_overrides_context.snap | 5 + ...ooter_status_line_overrides_shortcuts.snap | 5 + ...footer_status_line_truncated_with_gap.snap | 5 + .../tui/src/bottom_pane/status_line_setup.rs | 278 ++++++ codex-rs/tui/src/chatwidget.rs | 417 ++++++++- codex-rs/tui/src/chatwidget/tests.rs | 86 ++ codex-rs/tui/src/diff_render.rs | 2 +- codex-rs/tui/src/slash_command.rs | 3 + codex-rs/tui/src/status/mod.rs | 10 + codex-rs/tui/src/status/rate_limits.rs | 50 +- codex-rs/tui/src/text_formatting.rs | 43 + 25 files changed, 2324 insertions(+), 83 deletions(-) create mode 100644 codex-rs/tui/src/bottom_pane/multi_select_picker.rs create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_disabled_context_right.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_mode_right.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_no_mode_right.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_context.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_shortcuts.snap create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_truncated_with_gap.snap create mode 100644 codex-rs/tui/src/bottom_pane/status_line_setup.rs diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index a872d1b913..e9ac1c3b87 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1049,6 +1049,14 @@ "default": true, "description": "Show startup tooltips in the TUI welcome screen. Defaults to `true`.", "type": "boolean" + }, + "status_line": { + "default": null, + "description": "Ordered list of status line item identifiers.\n\nWhen set, the TUI renders the selected items as the status line.", + "items": { + "type": "string" + }, + "type": "array" } }, "type": "object" diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index cf87451821..94cd726711 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -55,6 +55,24 @@ pub enum ConfigEdit { ClearPath { segments: Vec }, } +pub fn status_line_items_edit(items: &[String]) -> ConfigEdit { + if items.is_empty() { + return ConfigEdit::ClearPath { + segments: vec!["tui".to_string(), "status_line".to_string()], + }; + } + + let mut array = toml_edit::Array::new(); + for item in items { + array.push(item.clone()); + } + + ConfigEdit::SetPath { + segments: vec!["tui".to_string(), "status_line".to_string()], + value: TomlItem::Value(array.into()), + } +} + // TODO(jif) move to a dedicated file mod document_helpers { use crate::config::types::McpServerConfig; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 94e36eb90b..78f947db68 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -224,6 +224,9 @@ pub struct Config { /// - `never`: Never use alternate screen (inline mode, preserves scrollback). pub tui_alternate_screen: AltScreenMode, + /// Ordered list of status line item identifiers for the TUI. + pub tui_status_line: Option>, + /// The directory that should be treated as the current working directory /// for the session. All relative paths inside the business-logic layer are /// resolved against this path. @@ -1712,6 +1715,7 @@ impl Config { .as_ref() .map(|t| t.alternate_screen) .unwrap_or_default(), + tui_status_line: cfg.tui.as_ref().and_then(|t| t.status_line.clone()), otel: { let t: OtelConfigToml = cfg.otel.unwrap_or_default(); let log_user_prompt = t.log_user_prompt.unwrap_or(false); @@ -1947,6 +1951,7 @@ persistence = "none" show_tooltips: true, experimental_mode: None, alternate_screen: AltScreenMode::Auto, + status_line: None, } ); } @@ -3897,6 +3902,7 @@ model_verbosity = "high" analytics_enabled: Some(true), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, + tui_status_line: None, otel: OtelConfig::default(), }, o3_profile_config @@ -3983,6 +3989,7 @@ model_verbosity = "high" analytics_enabled: Some(true), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, + tui_status_line: None, otel: OtelConfig::default(), }; @@ -4084,6 +4091,7 @@ model_verbosity = "high" analytics_enabled: Some(false), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, + tui_status_line: None, otel: OtelConfig::default(), }; @@ -4171,6 +4179,7 @@ model_verbosity = "high" analytics_enabled: Some(true), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, + tui_status_line: None, otel: OtelConfig::default(), }; diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index 7ffcf3a8e2..2eb868cc34 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -486,6 +486,12 @@ pub struct Tui { /// scrollback in terminal multiplexers like Zellij that follow the xterm spec. #[serde(default)] pub alternate_screen: AltScreenMode, + + /// Ordered list of status line item identifiers. + /// + /// When set, the TUI renders the selected items as the status line. + #[serde(default)] + pub status_line: Option>, } const fn default_true() -> bool { diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index e2d35dc08f..8c3bd5188c 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -535,6 +535,8 @@ pub(crate) struct App { /// Controls the animation thread that sends CommitTick events. pub(crate) commit_anim_running: Arc, + // Shared across ChatWidget instances so invalid status-line config warnings only emit once. + status_line_invalid_items_warned: Arc, // Esc-backtracking state grouped pub(crate) backtrack: crate::app_backtrack::BacktrackState, @@ -605,6 +607,7 @@ impl App { is_first_run: false, feedback_audience: self.feedback_audience, model: Some(self.chat_widget.current_model().to_string()), + status_line_invalid_items_warned: self.status_line_invalid_items_warned.clone(), otel_manager: self.otel_manager.clone(), } } @@ -906,6 +909,7 @@ impl App { for event in snapshot.events { self.handle_codex_event_replay(event); } + self.refresh_status_line(); } #[allow(clippy::too_many_arguments)] @@ -982,6 +986,15 @@ impl App { codex_core::terminal::user_agent(), SessionSource::Cli, ); + if config + .tui_status_line + .as_ref() + .is_some_and(|cmd| !cmd.is_empty()) + { + otel_manager.counter("codex.status_line", 1, &[]); + } + + let status_line_invalid_items_warned = Arc::new(AtomicBool::new(false)); let enhanced_keys_supported = tui.enhanced_keys_supported(); let mut chat_widget = match session_selection { @@ -1003,6 +1016,7 @@ impl App { is_first_run, feedback_audience, model: Some(model.clone()), + status_line_invalid_items_warned: status_line_invalid_items_warned.clone(), otel_manager: otel_manager.clone(), }; ChatWidget::new(init, thread_manager.clone()) @@ -1032,6 +1046,7 @@ impl App { is_first_run, feedback_audience, model: config.model.clone(), + status_line_invalid_items_warned: status_line_invalid_items_warned.clone(), otel_manager: otel_manager.clone(), }; ChatWidget::new_from_existing(init, resumed.thread, resumed.session_configured) @@ -1061,6 +1076,7 @@ impl App { is_first_run, feedback_audience, model: config.model.clone(), + status_line_invalid_items_warned: status_line_invalid_items_warned.clone(), otel_manager: otel_manager.clone(), }; ChatWidget::new_from_existing(init, forked.thread, forked.session_configured) @@ -1092,6 +1108,7 @@ impl App { deferred_history_lines: Vec::new(), has_emitted_history_lines: false, commit_anim_running: Arc::new(AtomicBool::new(false)), + status_line_invalid_items_warned: status_line_invalid_items_warned.clone(), backtrack: BacktrackState::default(), backtrack_render_pending: false, feedback: feedback.clone(), @@ -1220,6 +1237,13 @@ impl App { tui: &mut tui::Tui, event: TuiEvent, ) -> Result { + if matches!(event, TuiEvent::Draw) { + let size = tui.terminal.size()?; + if size != tui.terminal.last_known_screen_size { + self.refresh_status_line(); + } + } + if self.overlay.is_some() { let _ = self.handle_backtrack_overlay_event(tui, event).await?; } else { @@ -1293,6 +1317,7 @@ impl App { is_first_run: false, feedback_audience: self.feedback_audience, model: Some(model), + status_line_invalid_items_warned: self.status_line_invalid_items_warned.clone(), otel_manager: self.otel_manager.clone(), }; self.chat_widget = ChatWidget::new(init, self.server.clone()); @@ -1564,12 +1589,15 @@ impl App { } AppEvent::UpdateReasoningEffort(effort) => { self.on_update_reasoning_effort(effort); + self.refresh_status_line(); } AppEvent::UpdateModel(model) => { self.chat_widget.set_model(&model); + self.refresh_status_line(); } AppEvent::UpdateCollaborationMode(mask) => { self.chat_widget.set_collaboration_mask(mask); + self.refresh_status_line(); } AppEvent::UpdatePersonality(personality) => { self.on_update_personality(personality); @@ -2203,11 +2231,45 @@ impl App { )); } }, + AppEvent::StatusLineSetup { items } => { + let ids = items.iter().map(ToString::to_string).collect::>(); + let edit = codex_core::config::edit::status_line_items_edit(&ids); + let apply_result = ConfigEditsBuilder::new(&self.config.codex_home) + .with_edits([edit]) + .apply() + .await; + match apply_result { + Ok(()) => { + self.config.tui_status_line = if ids.is_empty() { + None + } else { + Some(ids.clone()) + }; + self.chat_widget.setup_status_line(items); + } + Err(err) => { + tracing::error!(error = %err, "failed to persist status line items; keeping previous selection"); + self.chat_widget + .add_error_message(format!("Failed to save status line items: {err}")); + } + } + } + AppEvent::StatusLineBranchUpdated { cwd, branch } => { + self.chat_widget.set_status_line_branch(cwd, branch); + self.refresh_status_line(); + } + AppEvent::StatusLineSetupCancelled => { + self.chat_widget.cancel_status_line_setup(); + } } Ok(AppRunControl::Continue) } fn handle_codex_event_now(&mut self, event: Event) { + let needs_refresh = matches!( + event.msg, + EventMsg::SessionConfigured(_) | EventMsg::TokenCount(_) + ); if self.suppress_shutdown_complete && matches!(event.msg, EventMsg::ShutdownComplete) { self.suppress_shutdown_complete = false; return; @@ -2219,6 +2281,10 @@ impl App { } self.handle_backtrack_event(&event.msg); self.chat_widget.handle_codex_event(event); + + if needs_refresh { + self.refresh_status_line(); + } } fn handle_codex_event_replay(&mut self, event: Event) { @@ -2473,6 +2539,10 @@ impl App { }; } + fn refresh_status_line(&mut self) { + self.chat_widget.refresh_status_line(); + } + #[cfg(target_os = "windows")] fn spawn_world_writable_scan( cwd: PathBuf, @@ -2629,6 +2699,7 @@ mod tests { has_emitted_history_lines: false, enhanced_keys_supported: false, commit_anim_running: Arc::new(AtomicBool::new(false)), + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), backtrack: BacktrackState::default(), backtrack_render_pending: false, feedback: codex_feedback::CodexFeedback::new(), @@ -2682,6 +2753,7 @@ mod tests { has_emitted_history_lines: false, enhanced_keys_supported: false, commit_anim_running: Arc::new(AtomicBool::new(false)), + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), backtrack: BacktrackState::default(), backtrack_render_pending: false, feedback: codex_feedback::CodexFeedback::new(), diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index bbd228e114..bd48e5de1b 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -19,6 +19,7 @@ use codex_protocol::ThreadId; use codex_protocol::openai_models::ModelPreset; use crate::bottom_pane::ApprovalRequest; +use crate::bottom_pane::StatusLineItem; use crate::history_cell::HistoryCell; use codex_core::features::Feature; @@ -292,6 +293,18 @@ pub(crate) enum AppEvent { /// Launch the external editor after a normal draw has completed. LaunchExternalEditor, + + /// Async update of the current git branch for status line rendering. + StatusLineBranchUpdated { + cwd: PathBuf, + branch: Option, + }, + /// Apply a user-confirmed status-line item ordering/selection. + StatusLineSetup { + items: Vec, + }, + /// Dismiss the status-line setup UI without changing config. + StatusLineSetupCancelled, } /// The exit strategy requested by the UI layer. diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 116e9c3abe..4afbfea01a 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -82,9 +82,12 @@ //! edits and renders a placeholder prompt instead of the editable textarea. This is part of the //! overall state machine, since it affects which transitions are even possible from a given UI //! state. +use crate::bottom_pane::footer::mode_indicator_line; +use crate::bottom_pane::selection_popup_common::truncate_line_with_ellipsis_if_overflow; use crate::key_hint; use crate::key_hint::KeyBinding; use crate::key_hint::has_ctrl_or_alt; +use crate::ui_consts::FOOTER_INDENT_COLS; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; @@ -118,6 +121,7 @@ use super::footer::footer_height; use super::footer::footer_hint_items_width; use super::footer::footer_line_width; use super::footer::inset_footer_hint_area; +use super::footer::max_left_width_for_right; use super::footer::render_context_right; use super::footer::render_footer_from_props; use super::footer::render_footer_hint_items; @@ -295,6 +299,8 @@ pub(crate) struct ChatComposer { connectors_enabled: bool, personality_command_enabled: bool, windows_degraded_sandbox_active: bool, + status_line_value: Option>, + status_line_enabled: bool, } #[derive(Clone, Debug)] @@ -384,6 +390,8 @@ impl ChatComposer { connectors_enabled: false, personality_command_enabled: false, windows_degraded_sandbox_active: false, + status_line_value: None, + status_line_enabled: false, }; // Apply configuration via the setter to keep side-effects centralized. this.set_disable_paste_burst(disable_paste_burst); @@ -458,7 +466,7 @@ impl ChatComposer { let footer_props = self.footer_props(); let footer_hint_height = self .custom_footer_height() - .unwrap_or_else(|| footer_height(footer_props)); + .unwrap_or_else(|| footer_height(&footer_props)); let footer_spacing = Self::footer_spacing(footer_hint_height); let footer_total_height = footer_hint_height + footer_spacing; let popup_constraint = match &self.active_popup { @@ -2524,6 +2532,8 @@ impl ChatComposer { is_wsl, context_window_percent: self.context_window_percent, context_window_used_tokens: self.context_window_used_tokens, + status_line_value: self.status_line_value.clone(), + status_line_enabled: self.status_line_enabled, } } @@ -3006,6 +3016,14 @@ impl ChatComposer { self.footer_mode = reset_mode_after_activity(self.footer_mode); } } + + pub(crate) fn set_status_line(&mut self, status_line: Option>) { + self.status_line_value = status_line; + } + + pub(crate) fn set_status_line_enabled(&mut self, enabled: bool) { + self.status_line_enabled = enabled; + } } fn skill_display_name(skill: &SkillMetadata) -> &str { @@ -3046,7 +3064,7 @@ impl Renderable for ChatComposer { let footer_props = self.footer_props(); let footer_hint_height = self .custom_footer_height() - .unwrap_or_else(|| footer_height(footer_props)); + .unwrap_or_else(|| footer_height(&footer_props)); let footer_spacing = Self::footer_spacing(footer_hint_height); let footer_total_height = footer_hint_height + footer_spacing; const COLS_WITH_MARGIN: u16 = LIVE_PREFIX_COLS + 1; @@ -3099,14 +3117,9 @@ impl ChatComposer { | FooterMode::ShortcutOverlay | FooterMode::EscHint => false, }; - let context_line = context_window_line( - footer_props.context_window_percent, - footer_props.context_window_used_tokens, - ); - let context_width = context_line.width() as u16; let custom_height = self.custom_footer_height(); let footer_hint_height = - custom_height.unwrap_or_else(|| footer_height(footer_props)); + custom_height.unwrap_or_else(|| footer_height(&footer_props)); let footer_spacing = Self::footer_spacing(footer_hint_height); let hint_rect = if footer_spacing > 0 && footer_hint_height > 0 { let [_, hint_rect] = Layout::vertical([ @@ -3118,24 +3131,80 @@ impl ChatComposer { } else { popup_rect }; - let left_width = if self.footer_flash_visible() { + let available_width = + hint_rect.width.saturating_sub(FOOTER_INDENT_COLS as u16) as usize; + let status_line = footer_props + .status_line_value + .as_ref() + .map(|line| line.clone().dim()); + let status_line_candidate = footer_props.status_line_enabled + && matches!( + footer_props.mode, + FooterMode::ComposerEmpty | FooterMode::ComposerHasDraft + ); + let mut truncated_status_line = if status_line_candidate { + status_line.as_ref().map(|line| { + truncate_line_with_ellipsis_if_overflow(line.clone(), available_width) + }) + } else { + None + }; + let status_line_active = status_line_candidate && truncated_status_line.is_some(); + let left_mode_indicator = if status_line_active { + None + } else { + self.collaboration_mode_indicator + }; + let mut left_width = if self.footer_flash_visible() { self.footer_flash .as_ref() .map(|flash| flash.line.width() as u16) .unwrap_or(0) } else if let Some(items) = self.footer_hint_override.as_ref() { footer_hint_items_width(items) + } else if status_line_active { + truncated_status_line + .as_ref() + .map(|line| line.width() as u16) + .unwrap_or(0) } else { footer_line_width( - footer_props, - self.collaboration_mode_indicator, + &footer_props, + left_mode_indicator, show_cycle_hint, show_shortcuts_hint, show_queue_hint, ) }; + let right_line = if status_line_active { + let full = + mode_indicator_line(self.collaboration_mode_indicator, show_cycle_hint); + let compact = mode_indicator_line(self.collaboration_mode_indicator, false); + let full_width = full.as_ref().map(|l| l.width() as u16).unwrap_or(0); + if can_show_left_with_context(hint_rect, left_width, full_width) { + full + } else { + compact + } + } else { + Some(context_window_line( + footer_props.context_window_percent, + footer_props.context_window_used_tokens, + )) + }; + let right_width = right_line.as_ref().map(|l| l.width() as u16).unwrap_or(0); + if status_line_active + && let Some(max_left) = max_left_width_for_right(hint_rect, right_width) + && left_width > max_left + && let Some(line) = status_line.as_ref().map(|line| { + truncate_line_with_ellipsis_if_overflow(line.clone(), max_left as usize) + }) + { + left_width = line.width() as u16; + truncated_status_line = Some(line); + } let can_show_left_and_context = - can_show_left_with_context(hint_rect, left_width, context_width); + can_show_left_with_context(hint_rect, left_width, right_width); let has_override = self.footer_flash_visible() || self.footer_hint_override.is_some(); let single_line_layout = if has_override { @@ -3149,8 +3218,8 @@ impl ChatComposer { // the context indicator on narrow widths. Some(single_line_footer_layout( hint_rect, - context_width, - self.collaboration_mode_indicator, + right_width, + left_mode_indicator, show_cycle_hint, show_shortcuts_hint, show_queue_hint, @@ -3161,7 +3230,7 @@ impl ChatComposer { | FooterMode::ShortcutOverlay => None, } }; - let show_context = if matches!( + let show_right = if matches!( footer_props.mode, FooterMode::EscHint | FooterMode::QuitShortcutReminder @@ -3178,15 +3247,31 @@ impl ChatComposer { if let Some((summary_left, _)) = single_line_layout { match summary_left { SummaryLeft::Default => { - render_footer_from_props( - hint_rect, - buf, - footer_props, - self.collaboration_mode_indicator, - show_cycle_hint, - show_shortcuts_hint, - show_queue_hint, - ); + if status_line_active { + if let Some(line) = truncated_status_line.clone() { + render_footer_line(hint_rect, buf, line); + } else { + render_footer_from_props( + hint_rect, + buf, + &footer_props, + left_mode_indicator, + show_cycle_hint, + show_shortcuts_hint, + show_queue_hint, + ); + } + } else { + render_footer_from_props( + hint_rect, + buf, + &footer_props, + left_mode_indicator, + show_cycle_hint, + show_shortcuts_hint, + show_queue_hint, + ); + } } SummaryLeft::Custom(line) => { render_footer_line(hint_rect, buf, line); @@ -3199,11 +3284,15 @@ impl ChatComposer { } } else if let Some(items) = self.footer_hint_override.as_ref() { render_footer_hint_items(hint_rect, buf, items); + } else if status_line_active { + if let Some(line) = truncated_status_line { + render_footer_line(hint_rect, buf, line); + } } else { render_footer_from_props( hint_rect, buf, - footer_props, + &footer_props, self.collaboration_mode_indicator, show_cycle_hint, show_shortcuts_hint, @@ -3211,8 +3300,8 @@ impl ChatComposer { ); } - if show_context { - render_context_right(hint_rect, buf, &context_line); + if show_right && let Some(line) = &right_line { + render_context_right(hint_rect, buf, line); } } } @@ -3488,7 +3577,7 @@ mod tests { ); setup(&mut composer); let footer_props = composer.footer_props(); - let footer_lines = footer_height(footer_props); + let footer_lines = footer_height(&footer_props); let footer_spacing = ChatComposer::footer_spacing(footer_lines); let height = footer_lines + footer_spacing + 8; let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap(); diff --git a/codex-rs/tui/src/bottom_pane/footer.rs b/codex-rs/tui/src/bottom_pane/footer.rs index 4893f2f6a0..f6f61acf4b 100644 --- a/codex-rs/tui/src/bottom_pane/footer.rs +++ b/codex-rs/tui/src/bottom_pane/footer.rs @@ -53,7 +53,7 @@ use ratatui::widgets::Widget; /// (`render_footer_from_props` or the single-line collapse logic). The footer /// treats these values as authoritative and does not attempt to infer missing /// state (for example, it does not query whether a task is running). -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub(crate) struct FooterProps { pub(crate) mode: FooterMode, pub(crate) esc_backtrack_hint: bool, @@ -68,6 +68,8 @@ pub(crate) struct FooterProps { pub(crate) quit_shortcut_key: KeyBinding, pub(crate) context_window_percent: Option, pub(crate) context_window_used_tokens: Option, + pub(crate) status_line_value: Option>, + pub(crate) status_line_enabled: bool, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -168,7 +170,7 @@ pub(crate) fn reset_mode_after_activity(current: FooterMode) -> FooterMode { } } -pub(crate) fn footer_height(props: FooterProps) -> u16 { +pub(crate) fn footer_height(props: &FooterProps) -> u16 { let show_shortcuts_hint = match props.mode { FooterMode::ComposerEmpty => true, FooterMode::QuitShortcutReminder @@ -206,7 +208,7 @@ pub(crate) fn render_footer_line(area: Rect, buf: &mut Buffer, line: Line<'stati pub(crate) fn render_footer_from_props( area: Rect, buf: &mut Buffer, - props: FooterProps, + props: &FooterProps, collaboration_mode_indicator: Option, show_cycle_hint: bool, show_shortcuts_hint: bool, @@ -448,6 +450,13 @@ pub(crate) fn single_line_footer_layout( (SummaryLeft::None, true) } +pub(crate) fn mode_indicator_line( + indicator: Option, + show_cycle_hint: bool, +) -> Option> { + indicator.map(|indicator| Line::from(vec![indicator.styled_span(show_cycle_hint)])) +} + fn right_aligned_x(area: Rect, content_width: u16) -> Option { if area.is_empty() { return None; @@ -471,6 +480,20 @@ fn right_aligned_x(area: Rect, content_width: u16) -> Option { ) } +pub(crate) fn max_left_width_for_right(area: Rect, right_width: u16) -> Option { + let context_x = right_aligned_x(area, right_width)?; + let left_start = area.x + FOOTER_INDENT_COLS as u16; + + // minimal one column gap between left and right + let gap = FOOTER_CONTEXT_GAP_COLS; + + if context_x <= left_start + gap { + return Some(0); + } + + Some(context_x.saturating_sub(left_start + gap)) +} + pub(crate) fn can_show_left_with_context(area: Rect, left_width: u16, context_width: u16) -> bool { let Some(context_x) = right_aligned_x(area, context_width) else { return true; @@ -534,12 +557,22 @@ pub(crate) fn render_footer_hint_items(area: Rect, buf: &mut Buffer, items: &[(S /// fallback decisions live in `single_line_footer_layout`; this function only /// formats the chosen/default content. fn footer_from_props_lines( - props: FooterProps, + props: &FooterProps, collaboration_mode_indicator: Option, show_cycle_hint: bool, show_shortcuts_hint: bool, show_queue_hint: bool, ) -> Vec> { + // If status line content is present, show it for base modes. + if props.status_line_enabled + && let Some(status_line) = &props.status_line_value + && matches!( + props.mode, + FooterMode::ComposerEmpty | FooterMode::ComposerHasDraft + ) + { + return vec![status_line.clone().dim()]; + } match props.mode { FooterMode::QuitShortcutReminder => { vec![quit_shortcut_reminder_line(props.quit_shortcut_key)] @@ -580,7 +613,7 @@ fn footer_from_props_lines( } pub(crate) fn footer_line_width( - props: FooterProps, + props: &FooterProps, collaboration_mode_indicator: Option, show_cycle_hint: bool, show_shortcuts_hint: bool, @@ -957,31 +990,27 @@ const SHORTCUTS: &[ShortcutDescriptor] = &[ #[cfg(test)] mod tests { use super::*; + use crate::bottom_pane::selection_popup_common::truncate_line_with_ellipsis_if_overflow; + use crate::test_backend::VT100Backend; use insta::assert_snapshot; use pretty_assertions::assert_eq; use ratatui::Terminal; + use ratatui::backend::Backend; use ratatui::backend::TestBackend; fn snapshot_footer(name: &str, props: FooterProps) { - snapshot_footer_with_mode_indicator(name, 80, props, None); + snapshot_footer_with_mode_indicator(name, 80, &props, None); } - fn snapshot_footer_with_mode_indicator( - name: &str, - width: u16, - props: FooterProps, + fn draw_footer_frame( + terminal: &mut Terminal, + height: u16, + props: &FooterProps, collaboration_mode_indicator: Option, ) { - let height = footer_height(props).max(1); - let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap(); terminal .draw(|f| { let area = Rect::new(0, 0, f.area().width, height); - let context_line = context_window_line( - props.context_window_percent, - props.context_window_used_tokens, - ); - let context_width = context_line.width() as u16; let show_cycle_hint = !props.is_task_running; let show_shortcuts_hint = match props.mode { FooterMode::ComposerEmpty => true, @@ -997,53 +1026,118 @@ mod tests { | FooterMode::ShortcutOverlay | FooterMode::EscHint => false, }; - let left_width = footer_line_width( - props, - collaboration_mode_indicator, - show_cycle_hint, - show_shortcuts_hint, - show_queue_hint, - ); + let left_mode_indicator = if props.status_line_enabled { + None + } else { + collaboration_mode_indicator + }; + let available_width = area.width.saturating_sub(FOOTER_INDENT_COLS as u16) as usize; + let mut truncated_status_line = if props.status_line_enabled + && matches!( + props.mode, + FooterMode::ComposerEmpty | FooterMode::ComposerHasDraft + ) { + props + .status_line_value + .as_ref() + .map(|line| line.clone().dim()) + .map(|line| truncate_line_with_ellipsis_if_overflow(line, available_width)) + } else { + None + }; + let mut left_width = if props.status_line_enabled { + truncated_status_line + .as_ref() + .map(|line| line.width() as u16) + .unwrap_or(0) + } else { + footer_line_width( + props, + left_mode_indicator, + show_cycle_hint, + show_shortcuts_hint, + show_queue_hint, + ) + }; + let right_line = if props.status_line_enabled { + let full = mode_indicator_line(collaboration_mode_indicator, show_cycle_hint); + let compact = mode_indicator_line(collaboration_mode_indicator, false); + let full_width = full.as_ref().map(|line| line.width() as u16).unwrap_or(0); + if can_show_left_with_context(area, left_width, full_width) { + full + } else { + compact + } + } else { + Some(context_window_line( + props.context_window_percent, + props.context_window_used_tokens, + )) + }; + let right_width = right_line + .as_ref() + .map(|line| line.width() as u16) + .unwrap_or(0); + if props.status_line_enabled + && let Some(max_left) = max_left_width_for_right(area, right_width) + && left_width > max_left + && let Some(line) = props + .status_line_value + .as_ref() + .map(|line| line.clone().dim()) + .map(|line| { + truncate_line_with_ellipsis_if_overflow(line, max_left as usize) + }) + { + left_width = line.width() as u16; + truncated_status_line = Some(line); + } let can_show_left_and_context = - can_show_left_with_context(area, left_width, context_width); + can_show_left_with_context(area, left_width, right_width); if matches!( props.mode, FooterMode::ComposerEmpty | FooterMode::ComposerHasDraft ) { let (summary_left, show_context) = single_line_footer_layout( area, - context_width, - collaboration_mode_indicator, + right_width, + left_mode_indicator, show_cycle_hint, show_shortcuts_hint, show_queue_hint, ); match summary_left { SummaryLeft::Default => { - render_footer_from_props( - area, - f.buffer_mut(), - props, - collaboration_mode_indicator, - show_cycle_hint, - show_shortcuts_hint, - show_queue_hint, - ); + if props.status_line_enabled { + if let Some(line) = truncated_status_line.clone() { + render_footer_line(area, f.buffer_mut(), line); + } + } else { + render_footer_from_props( + area, + f.buffer_mut(), + props, + left_mode_indicator, + show_cycle_hint, + show_shortcuts_hint, + show_queue_hint, + ); + } } SummaryLeft::Custom(line) => { render_footer_line(area, f.buffer_mut(), line); } SummaryLeft::None => {} } - if show_context { - render_context_right(area, f.buffer_mut(), &context_line); + if show_context && let Some(line) = &right_line { + render_context_right(area, f.buffer_mut(), line); } } else { render_footer_from_props( area, f.buffer_mut(), props, - collaboration_mode_indicator, + left_mode_indicator, show_cycle_hint, show_shortcuts_hint, show_queue_hint, @@ -1055,15 +1149,37 @@ mod tests { | FooterMode::QuitShortcutReminder | FooterMode::ShortcutOverlay ); - if show_context { - render_context_right(area, f.buffer_mut(), &context_line); + if show_context && let Some(line) = &right_line { + render_context_right(area, f.buffer_mut(), line); } } }) .unwrap(); + } + + fn snapshot_footer_with_mode_indicator( + name: &str, + width: u16, + props: &FooterProps, + collaboration_mode_indicator: Option, + ) { + let height = footer_height(props).max(1); + let mut terminal = Terminal::new(TestBackend::new(width, height)).unwrap(); + draw_footer_frame(&mut terminal, height, props, collaboration_mode_indicator); assert_snapshot!(name, terminal.backend()); } + fn render_footer_with_mode_indicator( + width: u16, + props: &FooterProps, + collaboration_mode_indicator: Option, + ) -> String { + let height = footer_height(props).max(1); + let mut terminal = Terminal::new(VT100Backend::new(width, height)).expect("terminal"); + draw_footer_frame(&mut terminal, height, props, collaboration_mode_indicator); + terminal.backend().vt100().screen().contents() + } + #[test] fn footer_snapshots() { snapshot_footer( @@ -1079,6 +1195,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1095,6 +1213,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1111,6 +1231,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1127,6 +1249,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1143,6 +1267,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1159,6 +1285,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1175,6 +1303,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1191,6 +1321,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: Some(72), context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1207,6 +1339,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: Some(123_456), + status_line_value: None, + status_line_enabled: false, }, ); @@ -1223,6 +1357,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1239,6 +1375,8 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }, ); @@ -1253,19 +1391,21 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }; snapshot_footer_with_mode_indicator( "footer_mode_indicator_wide", 120, - props, + &props, Some(CollaborationModeIndicator::Plan), ); snapshot_footer_with_mode_indicator( "footer_mode_indicator_narrow_overlap_hides", 50, - props, + &props, Some(CollaborationModeIndicator::Plan), ); @@ -1280,14 +1420,161 @@ mod tests { quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), context_window_percent: None, context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, }; snapshot_footer_with_mode_indicator( "footer_mode_indicator_running_hides_hint", 120, - props, + &props, Some(CollaborationModeIndicator::Plan), ); + + let props = FooterProps { + mode: FooterMode::ComposerEmpty, + esc_backtrack_hint: false, + use_shift_enter_hint: false, + is_task_running: false, + steer_enabled: false, + collaboration_modes_enabled: false, + is_wsl: false, + quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), + context_window_percent: None, + context_window_used_tokens: None, + status_line_value: Some(Line::from("Status line content".to_string())), + status_line_enabled: true, + }; + + snapshot_footer("footer_status_line_overrides_shortcuts", props); + + let props = FooterProps { + mode: FooterMode::ComposerEmpty, + esc_backtrack_hint: false, + use_shift_enter_hint: false, + is_task_running: false, + steer_enabled: false, + collaboration_modes_enabled: true, + is_wsl: false, + quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), + context_window_percent: Some(50), + context_window_used_tokens: None, + status_line_value: None, // command timed out / empty + status_line_enabled: true, + }; + + snapshot_footer_with_mode_indicator( + "footer_status_line_enabled_mode_right", + 120, + &props, + Some(CollaborationModeIndicator::Plan), + ); + + let props = FooterProps { + mode: FooterMode::ComposerEmpty, + esc_backtrack_hint: false, + use_shift_enter_hint: false, + is_task_running: false, + steer_enabled: false, + collaboration_modes_enabled: true, + is_wsl: false, + quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), + context_window_percent: Some(50), + context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: false, + }; + + snapshot_footer_with_mode_indicator( + "footer_status_line_disabled_context_right", + 120, + &props, + Some(CollaborationModeIndicator::Plan), + ); + + let props = FooterProps { + mode: FooterMode::ComposerEmpty, + esc_backtrack_hint: false, + use_shift_enter_hint: false, + is_task_running: false, + steer_enabled: false, + collaboration_modes_enabled: false, + is_wsl: false, + quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), + context_window_percent: Some(50), + context_window_used_tokens: None, + status_line_value: None, + status_line_enabled: true, + }; + + // has status line and no collaboration mode + snapshot_footer_with_mode_indicator( + "footer_status_line_enabled_no_mode_right", + 120, + &props, + None, + ); + + let props = FooterProps { + mode: FooterMode::ComposerEmpty, + esc_backtrack_hint: false, + use_shift_enter_hint: false, + is_task_running: false, + steer_enabled: false, + collaboration_modes_enabled: true, + is_wsl: false, + quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), + context_window_percent: Some(50), + context_window_used_tokens: None, + status_line_value: Some(Line::from( + "Status line content that should truncate before the mode indicator".to_string(), + )), + status_line_enabled: true, + }; + + snapshot_footer_with_mode_indicator( + "footer_status_line_truncated_with_gap", + 40, + &props, + Some(CollaborationModeIndicator::Plan), + ); + } + + #[test] + fn footer_status_line_truncates_to_keep_mode_indicator() { + let props = FooterProps { + mode: FooterMode::ComposerEmpty, + esc_backtrack_hint: false, + use_shift_enter_hint: false, + is_task_running: false, + steer_enabled: false, + collaboration_modes_enabled: true, + is_wsl: false, + quit_shortcut_key: key_hint::ctrl(KeyCode::Char('c')), + context_window_percent: Some(50), + context_window_used_tokens: None, + status_line_value: Some(Line::from( + "Status line content that is definitely too long to fit alongside the mode label" + .to_string(), + )), + status_line_enabled: true, + }; + + let screen = + render_footer_with_mode_indicator(80, &props, Some(CollaborationModeIndicator::Plan)); + let collapsed = screen.split_whitespace().collect::>().join(" "); + assert!( + collapsed.contains("Plan mode"), + "mode indicator should remain visible" + ); + assert!( + !collapsed.contains("shift+tab to cycle"), + "compact mode indicator should be used when space is tight" + ); + assert!( + screen.contains('…'), + "status line should be truncated with ellipsis to keep mode indicator" + ); } #[test] diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index b251beb80e..b9c77fc72d 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -36,11 +36,14 @@ use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use ratatui::buffer::Buffer; use ratatui::layout::Rect; +use ratatui::text::Line; use std::time::Duration; mod app_link_view; mod approval_overlay; +mod multi_select_picker; mod request_user_input; +mod status_line_setup; pub(crate) use app_link_view::AppLinkView; pub(crate) use approval_overlay::ApprovalOverlay; pub(crate) use approval_overlay::ApprovalRequest; @@ -74,6 +77,8 @@ pub(crate) use feedback_view::feedback_selection_params; pub(crate) use feedback_view::feedback_upload_consent_params; pub(crate) use skills_toggle_view::SkillsToggleItem; pub(crate) use skills_toggle_view::SkillsToggleView; +pub(crate) use status_line_setup::StatusLineItem; +pub(crate) use status_line_setup::StatusLineSetupView; mod paste_burst; pub mod popup_consts; mod queued_user_messages; @@ -862,6 +867,16 @@ impl BottomPane { RenderableItem::Owned(Box::new(flex2)) } } + + pub(crate) fn set_status_line(&mut self, status_line: Option>) { + self.composer.set_status_line(status_line); + self.request_redraw(); + } + + pub(crate) fn set_status_line_enabled(&mut self, enabled: bool) { + self.composer.set_status_line_enabled(enabled); + self.request_redraw(); + } } impl Renderable for BottomPane { diff --git a/codex-rs/tui/src/bottom_pane/multi_select_picker.rs b/codex-rs/tui/src/bottom_pane/multi_select_picker.rs new file mode 100644 index 0000000000..0fb027a57a --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/multi_select_picker.rs @@ -0,0 +1,795 @@ +//! Multi-select picker widget for selecting multiple items from a list. +//! +//! This module provides a fuzzy-searchable, scrollable picker that allows users +//! to toggle multiple items on/off. It supports: +//! +//! - **Fuzzy search**: Type to filter items by name +//! - **Toggle selection**: Space to toggle items on/off +//! - **Reordering**: Optional left/right arrow support to reorder items +//! - **Live preview**: Optional callback to show a preview of current selections +//! - **Callbacks**: Hooks for change, confirm, and cancel events +//! +//! # Example +//! +//! ```ignore +//! let picker = MultiSelectPicker::new( +//! "Select Items".to_string(), +//! Some("Choose which items to enable".to_string()), +//! app_event_tx, +//! ) +//! .items(vec![ +//! MultiSelectItem { id: "a".into(), name: "Item A".into(), description: None, enabled: true }, +//! MultiSelectItem { id: "b".into(), name: "Item B".into(), description: None, enabled: false }, +//! ]) +//! .on_confirm(|selected_ids, tx| { /* handle confirmation */ }) +//! .build(); +//! ``` + +use codex_common::fuzzy_match::fuzzy_match; +use crossterm::event::KeyCode; +use crossterm::event::KeyEvent; +use crossterm::event::KeyModifiers; +use ratatui::buffer::Buffer; +use ratatui::layout::Constraint; +use ratatui::layout::Layout; +use ratatui::layout::Rect; +use ratatui::style::Stylize; +use ratatui::text::Line; +use ratatui::text::Span; +use ratatui::widgets::Block; +use ratatui::widgets::Widget; + +use super::selection_popup_common::GenericDisplayRow; +use crate::app_event_sender::AppEventSender; +use crate::bottom_pane::CancellationEvent; +use crate::bottom_pane::bottom_pane_view::BottomPaneView; +use crate::bottom_pane::popup_consts::MAX_POPUP_ROWS; +use crate::bottom_pane::scroll_state::ScrollState; +use crate::bottom_pane::selection_popup_common::render_rows_single_line; +use crate::bottom_pane::selection_popup_common::truncate_line_with_ellipsis_if_overflow; +use crate::key_hint; +use crate::render::Insets; +use crate::render::RectExt; +use crate::render::renderable::ColumnRenderable; +use crate::render::renderable::Renderable; +use crate::style::user_message_style; +use crate::text_formatting::truncate_text; + +/// Maximum display length for item names before truncation. +const ITEM_NAME_TRUNCATE_LEN: usize = 21; + +/// Placeholder text shown in the search input when empty. +const SEARCH_PLACEHOLDER: &str = "Type to search"; + +/// Prefix displayed before the search query (mimics a command prompt). +const SEARCH_PROMPT_PREFIX: &str = "> "; + +/// Direction for reordering items in the list. +enum Direction { + Up, + Down, +} + +/// Callback invoked when any item's state changes (toggled or reordered). +/// Receives the full list of items and the event sender. +pub type ChangeCallBack = Box; + +/// Callback invoked when the user confirms their selection (presses Enter). +/// Receives a list of IDs for all enabled items. +pub type ConfirmCallback = Box; + +/// Callback invoked when the user cancels the picker (presses Escape). +pub type CancelCallback = Box; + +/// Callback to generate an optional preview line based on current item states. +/// Returns `None` to hide the preview area. +pub type PreviewCallback = Box Option> + Send + Sync>; + +/// A single selectable item in the multi-select picker. +/// +/// Each item has a unique identifier, display name, optional description, +/// and an enabled/disabled state that can be toggled by the user. +#[derive(Default)] +pub(crate) struct MultiSelectItem { + /// Unique identifier returned in the confirm callback when this item is enabled. + pub id: String, + + /// Display name shown in the picker list. Will be truncated if too long. + pub name: String, + + /// Optional description shown alongside the name (dimmed). + pub description: Option, + + /// Whether this item is currently selected/enabled. + pub enabled: bool, +} + +/// A multi-select picker widget with fuzzy search and optional reordering. +/// +/// The picker displays a scrollable list of items with checkboxes. Users can: +/// - Type to fuzzy-search and filter the list +/// - Use Up/Down (or Ctrl+P/Ctrl+N) to navigate +/// - Press Space to toggle the selected item +/// - Press Enter to confirm and close +/// - Press Escape to cancel and close +/// - Use Left/Right arrows to reorder items (if ordering is enabled) +/// +/// Create instances using the builder pattern via [`MultiSelectPicker::new`]. +pub(crate) struct MultiSelectPicker { + /// All items in the picker (unfiltered). + items: Vec, + + /// Scroll and selection state for the visible list. + state: ScrollState, + + /// Whether the picker has been closed (confirmed or cancelled). + pub(crate) complete: bool, + + /// Channel for sending application events. + app_event_tx: AppEventSender, + + /// Header widget displaying title and subtitle. + header: Box, + + /// Footer line showing keyboard hints. + footer_hint: Line<'static>, + + /// Current search/filter query entered by the user. + search_query: String, + + /// Indices into `items` that match the current filter, in display order. + filtered_indices: Vec, + + /// Whether left/right arrow reordering is enabled. + ordering_enabled: bool, + + /// Optional callback to generate a preview line from current item states. + preview_builder: Option, + + /// Cached preview line (updated on item changes). + preview_line: Option>, + + /// Callback invoked when items change (toggle or reorder). + on_change: Option, + + /// Callback invoked when the user confirms their selection. + on_confirm: Option, + + /// Callback invoked when the user cancels the picker. + on_cancel: Option, +} + +impl MultiSelectPicker { + /// Creates a new builder for constructing a `MultiSelectPicker`. + /// + /// # Arguments + /// + /// * `title` - The main title displayed at the top of the picker + /// * `subtitle` - Optional subtitle displayed below the title (dimmed) + /// * `app_event_tx` - Event sender for dispatching application events + pub fn builder( + title: String, + subtitle: Option, + app_event_tx: AppEventSender, + ) -> MultiSelectPickerBuilder { + MultiSelectPickerBuilder::new(title, subtitle, app_event_tx) + } + + /// Applies the current search query to filter and sort items. + /// + /// Updates `filtered_indices` to contain only matching items, sorted by + /// fuzzy match score. Attempts to preserve the current selection if it + /// still matches the filter. + fn apply_filter(&mut self) { + // Filter + sort while preserving the current selection when possible. + let previously_selected = self + .state + .selected_idx + .and_then(|visible_idx| self.filtered_indices.get(visible_idx).copied()); + + let filter = self.search_query.trim(); + if filter.is_empty() { + self.filtered_indices = (0..self.items.len()).collect(); + } else { + let mut matches: Vec<(usize, i32)> = Vec::new(); + for (idx, item) in self.items.iter().enumerate() { + let display_name = item.name.as_str(); + if let Some((_indices, score)) = match_item(filter, display_name, &item.name) { + matches.push((idx, score)); + } + } + + matches.sort_by(|a, b| { + a.1.cmp(&b.1).then_with(|| { + let an = self.items[a.0].name.as_str(); + let bn = self.items[b.0].name.as_str(); + an.cmp(bn) + }) + }); + + self.filtered_indices = matches.into_iter().map(|(idx, _score)| idx).collect(); + } + + let len = self.filtered_indices.len(); + self.state.selected_idx = previously_selected + .and_then(|actual_idx| { + self.filtered_indices + .iter() + .position(|idx| *idx == actual_idx) + }) + .or_else(|| (len > 0).then_some(0)); + + let visible = Self::max_visible_rows(len); + self.state.clamp_selection(len); + self.state.ensure_visible(len, visible); + } + + /// Returns the number of items visible after filtering. + fn visible_len(&self) -> usize { + self.filtered_indices.len() + } + + /// Returns the maximum number of rows that can be displayed at once. + fn max_visible_rows(len: usize) -> usize { + MAX_POPUP_ROWS.min(len.max(1)) + } + + /// Calculates the width available for row content (accounts for borders). + fn rows_width(total_width: u16) -> u16 { + total_width.saturating_sub(2) + } + + /// Calculates the height needed for the row list area. + fn rows_height(&self, rows: &[GenericDisplayRow]) -> u16 { + rows.len().clamp(1, MAX_POPUP_ROWS).try_into().unwrap_or(1) + } + + /// Builds the display rows for all currently visible (filtered) items. + /// + /// Each row shows: `› [x] Item Name` where `›` indicates cursor position + /// and `[x]` or `[ ]` indicates enabled/disabled state. + fn build_rows(&self) -> Vec { + self.filtered_indices + .iter() + .enumerate() + .filter_map(|(visible_idx, actual_idx)| { + self.items.get(*actual_idx).map(|item| { + let is_selected = self.state.selected_idx == Some(visible_idx); + let prefix = if is_selected { '›' } else { ' ' }; + let marker = if item.enabled { 'x' } else { ' ' }; + let item_name = truncate_text(&item.name, ITEM_NAME_TRUNCATE_LEN); + let name = format!("{prefix} [{marker}] {item_name}"); + GenericDisplayRow { + name, + description: item.description.clone(), + ..Default::default() + } + }) + }) + .collect() + } + + /// Moves the selection cursor up, wrapping to the bottom if at the top. + fn move_up(&mut self) { + let len = self.visible_len(); + self.state.move_up_wrap(len); + let visible = Self::max_visible_rows(len); + self.state.ensure_visible(len, visible); + } + + /// Moves the selection cursor down, wrapping to the top if at the bottom. + fn move_down(&mut self) { + let len = self.visible_len(); + self.state.move_down_wrap(len); + let visible = Self::max_visible_rows(len); + self.state.ensure_visible(len, visible); + } + + /// Toggles the enabled state of the currently selected item. + /// + /// Updates the preview line and invokes the `on_change` callback if set. + fn toggle_selected(&mut self) { + let Some(idx) = self.state.selected_idx else { + return; + }; + let Some(actual_idx) = self.filtered_indices.get(idx).copied() else { + return; + }; + let Some(item) = self.items.get_mut(actual_idx) else { + return; + }; + + item.enabled = !item.enabled; + self.update_preview_line(); + if let Some(on_change) = &self.on_change { + on_change(&self.items, &self.app_event_tx); + } + } + + /// Confirms the current selection and closes the picker. + /// + /// Collects the IDs of all enabled items and passes them to the + /// `on_confirm` callback. Does nothing if already complete. + fn confirm_selection(&mut self) { + if self.complete { + return; + } + self.complete = true; + + if let Some(on_confirm) = &self.on_confirm { + let selected_ids: Vec = self + .items + .iter() + .filter(|item| item.enabled) + .map(|item| item.id.clone()) + .collect(); + on_confirm(&selected_ids, &self.app_event_tx); + } + } + + /// Moves the currently selected item up or down in the list. + /// + /// Only works when: + /// - The search query is empty (reordering is disabled during filtering) + /// - Ordering is enabled via [`MultiSelectPickerBuilder::enable_ordering`] + /// + /// Updates the preview line and invokes the `on_change` callback. + fn move_selected_item(&mut self, direction: Direction) { + if !self.search_query.is_empty() { + return; + } + + let Some(visible_idx) = self.state.selected_idx else { + return; + }; + let Some(actual_idx) = self.filtered_indices.get(visible_idx).copied() else { + return; + }; + + let len = self.items.len(); + if len == 0 { + return; + } + + let new_idx = match direction { + Direction::Up if actual_idx > 0 => actual_idx - 1, + Direction::Down if actual_idx + 1 < len => actual_idx + 1, + _ => return, + }; + + // move item in underlying list + self.items.swap(actual_idx, new_idx); + + self.update_preview_line(); + if let Some(on_change) = &self.on_change { + on_change(&self.items, &self.app_event_tx); + } + + // rebuild filtered indices to keep search/filter consistent + self.apply_filter(); + + // restore selection to moved item + let moved_idx = new_idx; + if let Some(new_visible_idx) = self + .filtered_indices + .iter() + .position(|idx| *idx == moved_idx) + { + self.state.selected_idx = Some(new_visible_idx); + } + } + + /// Regenerates the preview line using the preview callback. + /// + /// Called after any item state change (toggle or reorder). + fn update_preview_line(&mut self) { + self.preview_line = self + .preview_builder + .as_ref() + .and_then(|builder| builder(&self.items)); + } + + /// Closes the picker without confirming, invoking the `on_cancel` callback. + /// + /// Does nothing if already complete. + pub fn close(&mut self) { + if self.complete { + return; + } + self.complete = true; + if let Some(on_cancel) = &self.on_cancel { + on_cancel(&self.app_event_tx); + } + } +} + +impl BottomPaneView for MultiSelectPicker { + fn is_complete(&self) -> bool { + self.complete + } + + fn on_ctrl_c(&mut self) -> CancellationEvent { + self.close(); + CancellationEvent::Handled + } + + fn handle_key_event(&mut self, key_event: KeyEvent) { + match key_event { + KeyEvent { code: KeyCode::Left, .. } if self.ordering_enabled => { + self.move_selected_item(Direction::Up); + } + KeyEvent { code: KeyCode::Right, .. } if self.ordering_enabled => { + self.move_selected_item(Direction::Down); + } + KeyEvent { + code: KeyCode::Up, .. + } + | KeyEvent { + code: KeyCode::Char('p'), + modifiers: KeyModifiers::CONTROL, + .. + } + | KeyEvent { + code: KeyCode::Char('k'), + modifiers: KeyModifiers::CONTROL, + .. + } + | KeyEvent { + code: KeyCode::Char('\u{0010}'), + modifiers: KeyModifiers::NONE, + .. + } /* ^P */ => self.move_up(), + KeyEvent { + code: KeyCode::Down, + .. + } + | KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::CONTROL, + .. + } + | KeyEvent { + code: KeyCode::Char('n'), + modifiers: KeyModifiers::CONTROL, + .. + } + | KeyEvent { + code: KeyCode::Char('\u{000e}'), + modifiers: KeyModifiers::NONE, + .. + } /* ^N */ => self.move_down(), + KeyEvent { + code: KeyCode::Backspace, + .. + } => { + self.search_query.pop(); + self.apply_filter(); + } + KeyEvent { + code: KeyCode::Char(' '), + modifiers: KeyModifiers::NONE, + .. + } => self.toggle_selected(), + KeyEvent { + code: KeyCode::Enter, + .. + } => self.confirm_selection(), + KeyEvent { + code: KeyCode::Esc, .. + } => { + self.close(); + } + KeyEvent { + code: KeyCode::Char(c), + modifiers, + .. + } if !modifiers.contains(KeyModifiers::CONTROL) + && !modifiers.contains(KeyModifiers::ALT) => + { + self.search_query.push(c); + self.apply_filter(); + } + _ => {} + } + } +} + +impl Renderable for MultiSelectPicker { + fn desired_height(&self, width: u16) -> u16 { + let rows = self.build_rows(); + let rows_height = self.rows_height(&rows); + let preview_height = if self.preview_line.is_some() { 1 } else { 0 }; + + let mut height = self.header.desired_height(width.saturating_sub(4)); + height = height.saturating_add(rows_height + 3); + height = height.saturating_add(2); + height.saturating_add(1 + preview_height) + } + + fn render(&self, area: Rect, buf: &mut Buffer) { + if area.height == 0 || area.width == 0 { + return; + } + + // Reserve the footer line for the key-hint row. + let preview_height = if self.preview_line.is_some() { 1 } else { 0 }; + let footer_height = 1 + preview_height; + let [content_area, footer_area] = + Layout::vertical([Constraint::Fill(1), Constraint::Length(footer_height)]).areas(area); + + Block::default() + .style(user_message_style()) + .render(content_area, buf); + + let header_height = self + .header + .desired_height(content_area.width.saturating_sub(4)); + let rows = self.build_rows(); + let rows_width = Self::rows_width(content_area.width); + let rows_height = self.rows_height(&rows); + let [header_area, _, search_area, list_area] = Layout::vertical([ + Constraint::Max(header_height), + Constraint::Max(1), + Constraint::Length(2), + Constraint::Length(rows_height), + ]) + .areas(content_area.inset(Insets::vh(1, 2))); + + self.header.render(header_area, buf); + + // Render the search prompt as two lines to mimic the composer. + if search_area.height >= 2 { + let [placeholder_area, input_area] = + Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(search_area); + Line::from(SEARCH_PLACEHOLDER.dim()).render(placeholder_area, buf); + let line = if self.search_query.is_empty() { + Line::from(vec![SEARCH_PROMPT_PREFIX.dim()]) + } else { + Line::from(vec![ + SEARCH_PROMPT_PREFIX.dim(), + self.search_query.clone().into(), + ]) + }; + line.render(input_area, buf); + } else if search_area.height > 0 { + let query_span = if self.search_query.is_empty() { + SEARCH_PLACEHOLDER.dim() + } else { + self.search_query.clone().into() + }; + Line::from(query_span).render(search_area, buf); + } + + if list_area.height > 0 { + let render_area = Rect { + x: list_area.x.saturating_sub(2), + y: list_area.y, + width: rows_width.max(1), + height: list_area.height, + }; + render_rows_single_line( + render_area, + buf, + &rows, + &self.state, + render_area.height as usize, + "no matches", + ); + } + + let hint_area = if let Some(preview_line) = &self.preview_line { + let [preview_area, hint_area] = + Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(footer_area); + let preview_area = Rect { + x: preview_area.x + 2, + y: preview_area.y, + width: preview_area.width.saturating_sub(2), + height: preview_area.height, + }; + let max_preview_width = preview_area.width.saturating_sub(2) as usize; + let preview_line = + truncate_line_with_ellipsis_if_overflow(preview_line.clone(), max_preview_width); + preview_line.render(preview_area, buf); + hint_area + } else { + footer_area + }; + let hint_area = Rect { + x: hint_area.x + 2, + y: hint_area.y, + width: hint_area.width.saturating_sub(2), + height: hint_area.height, + }; + self.footer_hint.clone().dim().render(hint_area, buf); + } +} + +/// Builder for constructing a [`MultiSelectPicker`] with a fluent API. +/// +/// # Example +/// +/// ```ignore +/// let picker = MultiSelectPicker::new("Title".into(), None, tx) +/// .items(items) +/// .enable_ordering() +/// .on_preview(|items| Some(Line::from("Preview"))) +/// .on_confirm(|ids, tx| { /* handle */ }) +/// .on_cancel(|tx| { /* handle */ }) +/// .build(); +/// ``` +pub(crate) struct MultiSelectPickerBuilder { + title: String, + subtitle: Option, + instructions: Vec>, + items: Vec, + ordering_enabled: bool, + app_event_tx: AppEventSender, + preview_builder: Option, + on_change: Option, + on_confirm: Option, + on_cancel: Option, +} + +impl MultiSelectPickerBuilder { + /// Creates a new builder with the given title, optional subtitle, and event sender. + pub fn new(title: String, subtitle: Option, app_event_tx: AppEventSender) -> Self { + Self { + title, + subtitle, + instructions: Vec::new(), + items: Vec::new(), + ordering_enabled: false, + app_event_tx, + preview_builder: None, + on_change: None, + on_confirm: None, + on_cancel: None, + } + } + + /// Sets the list of selectable items. + pub fn items(mut self, items: Vec) -> Self { + self.items = items; + self + } + + /// Sets custom instruction spans for the footer hint line. + /// + /// If not set, default instructions are shown (Space to toggle, Enter to + /// confirm, Escape to close). + pub fn instructions(mut self, instructions: Vec>) -> Self { + self.instructions = instructions; + self + } + + /// Enables left/right arrow keys for reordering items. + /// + /// Reordering is only active when the search query is empty. + pub fn enable_ordering(mut self) -> Self { + self.ordering_enabled = true; + self + } + + /// Sets a callback to generate a preview line from the current item states. + /// + /// The callback receives all items and should return a [`Line`] to display, + /// or `None` to hide the preview area. + pub fn on_preview(mut self, callback: F) -> Self + where + F: Fn(&[MultiSelectItem]) -> Option> + Send + Sync + 'static, + { + self.preview_builder = Some(Box::new(callback)); + self + } + + /// Sets a callback invoked whenever an item's state changes. + /// + /// This includes both toggles and reordering operations. + #[allow(dead_code)] + pub fn on_change(mut self, callback: F) -> Self + where + F: Fn(&[MultiSelectItem], &AppEventSender) + Send + Sync + 'static, + { + self.on_change = Some(Box::new(callback)); + self + } + + /// Sets a callback invoked when the user confirms their selection (Enter). + /// + /// The callback receives a list of IDs for all enabled items. + pub fn on_confirm(mut self, callback: F) -> Self + where + F: Fn(&[String], &AppEventSender) + Send + Sync + 'static, + { + self.on_confirm = Some(Box::new(callback)); + self + } + + /// Sets a callback invoked when the user cancels the picker (Escape). + pub fn on_cancel(mut self, callback: F) -> Self + where + F: Fn(&AppEventSender) + Send + Sync + 'static, + { + self.on_cancel = Some(Box::new(callback)); + self + } + + /// Builds the [`MultiSelectPicker`] with all configured options. + /// + /// Initializes the filter to show all items and generates the initial + /// preview line if a preview callback was set. + pub fn build(self) -> MultiSelectPicker { + let mut header = ColumnRenderable::new(); + header.push(Line::from(self.title.bold())); + + if let Some(subtitle) = self.subtitle { + header.push(Line::from(subtitle.dim())); + } + + let instructions = if self.instructions.is_empty() { + vec![ + "Press ".into(), + key_hint::plain(KeyCode::Char(' ')).into(), + " to toggle; ".into(), + key_hint::plain(KeyCode::Enter).into(), + " to confirm and close; ".into(), + key_hint::plain(KeyCode::Esc).into(), + " to close".into(), + ] + } else { + self.instructions + }; + + let mut view = MultiSelectPicker { + items: self.items, + state: ScrollState::new(), + complete: false, + app_event_tx: self.app_event_tx, + header: Box::new(header), + footer_hint: Line::from(instructions), + ordering_enabled: self.ordering_enabled, + search_query: String::new(), + filtered_indices: Vec::new(), + preview_builder: self.preview_builder, + preview_line: None, + on_change: self.on_change, + on_confirm: self.on_confirm, + on_cancel: self.on_cancel, + }; + view.apply_filter(); + view.update_preview_line(); + view + } +} + +/// Performs fuzzy matching on an item against a filter string. +/// +/// Tries to match against the display name first, then falls back to name if different. Returns +/// the matching character indices (if matched on display name) and a score for sorting. +/// +/// # Arguments +/// +/// * `filter` - The search query to match against +/// * `display_name` - The primary name to match (shown to user) +/// * `name` - A secondary/canonical name to try if display name doesn't match +/// +/// # Returns +/// +/// * `Some((Some(indices), score))` - Matched on display name with highlight indices +/// * `Some((None, score))` - Matched on skill name only (no highlights for display) +/// * `None` - No match +pub(crate) fn match_item( + filter: &str, + display_name: &str, + name: &str, +) -> Option<(Option>, i32)> { + if let Some((indices, score)) = fuzzy_match(display_name, filter) { + return Some((Some(indices), score)); + } + if display_name != name + && let Some((_indices, score)) = fuzzy_match(name, filter) + { + return Some((None, score)); + } + None +} diff --git a/codex-rs/tui/src/bottom_pane/selection_popup_common.rs b/codex-rs/tui/src/bottom_pane/selection_popup_common.rs index f8e1ad8902..3f827dc41f 100644 --- a/codex-rs/tui/src/bottom_pane/selection_popup_common.rs +++ b/codex-rs/tui/src/bottom_pane/selection_popup_common.rs @@ -106,15 +106,20 @@ fn line_width(line: &Line<'_>) -> usize { .sum() } -fn truncate_line_to_width(line: Line<'static>, max_width: usize) -> Line<'static> { +pub(crate) fn truncate_line_to_width(line: Line<'static>, max_width: usize) -> Line<'static> { if max_width == 0 { return Line::from(Vec::>::new()); } + let Line { + style, + alignment, + spans, + } = line; let mut used = 0usize; let mut spans_out: Vec> = Vec::new(); - for span in line.spans { + for span in spans { let text = span.content.into_owned(); let style = span.style; let span_width = UnicodeWidthStr::width(text.as_str()); @@ -151,10 +156,17 @@ fn truncate_line_to_width(line: Line<'static>, max_width: usize) -> Line<'static break; } - Line::from(spans_out) + Line { + style, + alignment, + spans: spans_out, + } } -fn truncate_line_with_ellipsis_if_overflow(line: Line<'static>, max_width: usize) -> Line<'static> { +pub(crate) fn truncate_line_with_ellipsis_if_overflow( + line: Line<'static>, + max_width: usize, +) -> Line<'static> { if max_width == 0 { return Line::from(Vec::>::new()); } @@ -165,10 +177,18 @@ fn truncate_line_with_ellipsis_if_overflow(line: Line<'static>, max_width: usize } let truncated = truncate_line_to_width(line, max_width.saturating_sub(1)); - let mut spans = truncated.spans; + let Line { + style, + alignment, + mut spans, + } = truncated; let ellipsis_style = spans.last().map(|span| span.style).unwrap_or_default(); spans.push(Span::styled("…", ellipsis_style)); - Line::from(spans) + Line { + style, + alignment, + spans, + } } /// Computes the shared start column used for descriptions in selection rows. diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_disabled_context_right.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_disabled_context_right.snap new file mode 100644 index 0000000000..b86792ac77 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_disabled_context_right.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/bottom_pane/footer.rs +expression: terminal.backend() +--- +" ? for shortcuts · Plan mode (shift+tab to cycle) 50% context left " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_mode_right.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_mode_right.snap new file mode 100644 index 0000000000..2da49eeb64 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_mode_right.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/bottom_pane/footer.rs +expression: terminal.backend() +--- +" Plan mode (shift+tab to cycle) " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_no_mode_right.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_no_mode_right.snap new file mode 100644 index 0000000000..6813891613 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_enabled_no_mode_right.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/bottom_pane/footer.rs +expression: terminal.backend() +--- +" " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_context.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_context.snap new file mode 100644 index 0000000000..d3958253e3 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_context.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/bottom_pane/footer.rs +expression: terminal.backend() +--- +" Italic text " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_shortcuts.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_shortcuts.snap new file mode 100644 index 0000000000..bb0e2d33b9 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_overrides_shortcuts.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/bottom_pane/footer.rs +expression: terminal.backend() +--- +" Status line content " diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_truncated_with_gap.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_truncated_with_gap.snap new file mode 100644 index 0000000000..cef1531fd6 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__footer__tests__footer_status_line_truncated_with_gap.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/bottom_pane/footer.rs +expression: terminal.backend() +--- +" Status line content that … Plan mode " diff --git a/codex-rs/tui/src/bottom_pane/status_line_setup.rs b/codex-rs/tui/src/bottom_pane/status_line_setup.rs new file mode 100644 index 0000000000..29bcf7b9c9 --- /dev/null +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -0,0 +1,278 @@ +//! Status line configuration view for customizing the TUI status bar. +//! +//! This module provides an interactive picker for selecting which items appear +//! in the status line at the bottom of the terminal. Users can: +//! +//! - **Select items**: Toggle which information is displayed +//! - **Reorder items**: Use left/right arrows to change display order +//! - **Preview changes**: See a live preview of the configured status line +//! +//! # Available Status Line Items +//! +//! - Model information (name, reasoning level) +//! - Directory paths (current dir, project root) +//! - Git information (branch name) +//! - Context usage (remaining %, used %, window size) +//! - Usage limits (5-hour, weekly) +//! - Session info (ID, tokens used) +//! - Application version + +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::text::Line; +use std::collections::HashSet; +use strum::IntoEnumIterator; +use strum_macros::Display; +use strum_macros::EnumIter; +use strum_macros::EnumString; + +use crate::app_event::AppEvent; +use crate::app_event_sender::AppEventSender; +use crate::bottom_pane::CancellationEvent; +use crate::bottom_pane::bottom_pane_view::BottomPaneView; +use crate::bottom_pane::multi_select_picker::MultiSelectItem; +use crate::bottom_pane::multi_select_picker::MultiSelectPicker; +use crate::render::renderable::Renderable; + +/// Available items that can be displayed in the status line. +/// +/// Each variant represents a piece of information that can be shown at the +/// bottom of the TUI. Items are serialized to kebab-case for configuration +/// storage (e.g., `ModelWithReasoning` becomes `model-with-reasoning`). +/// +/// Some items are conditionally displayed based on availability: +/// - Git-related items only show when in a git repository +/// - Context/limit items only show when data is available from the API +/// - Session ID only shows after a session has started +#[derive(EnumIter, EnumString, Display, Debug, Clone, Eq, PartialEq)] +#[strum(serialize_all = "kebab_case")] +pub(crate) enum StatusLineItem { + /// The current model name. + ModelName, + + /// Model name with reasoning level suffix. + ModelWithReasoning, + + /// Current working directory path. + CurrentDir, + + /// Project root directory (if detected). + ProjectRoot, + + /// Current git branch name (if in a repository). + GitBranch, + + /// Percentage of context window remaining. + ContextRemaining, + + /// Percentage of context window used. + ContextUsed, + + /// Remaining usage on the 5-hour rate limit. + FiveHourLimit, + + /// Remaining usage on the weekly rate limit. + WeeklyLimit, + + /// Codex application version. + CodexVersion, + + /// Total context window size in tokens. + ContextWindowSize, + + /// Total tokens used in the current session. + UsedTokens, + + /// Total input tokens consumed. + TotalInputTokens, + + /// Total output tokens generated. + TotalOutputTokens, + + /// Full session UUID. + SessionId, +} + +impl StatusLineItem { + /// User-visible description shown in the popup. + pub(crate) fn description(&self) -> &'static str { + match self { + StatusLineItem::ModelName => "Current model name", + StatusLineItem::ModelWithReasoning => "Current model name with reasoning level", + StatusLineItem::CurrentDir => "Current working directory", + StatusLineItem::ProjectRoot => "Project root directory (omitted when unavailable)", + StatusLineItem::GitBranch => "Current Git branch (omitted when unavailable)", + StatusLineItem::ContextRemaining => { + "Percentage of context window remaining (omitted when unknown)" + } + StatusLineItem::ContextUsed => { + "Percentage of context window used (omitted when unknown)" + } + StatusLineItem::FiveHourLimit => { + "Remaining usage on 5-hour usage limit (omitted when unavailable)" + } + StatusLineItem::WeeklyLimit => { + "Remaining usage on weekly usage limit (omitted when unavailable)" + } + StatusLineItem::CodexVersion => "Codex application version", + StatusLineItem::ContextWindowSize => { + "Total context window size in tokens (omitted when unknown)" + } + StatusLineItem::UsedTokens => "Total tokens used in session (omitted when zero)", + StatusLineItem::TotalInputTokens => "Total input tokens used in session", + StatusLineItem::TotalOutputTokens => "Total output tokens used in session", + StatusLineItem::SessionId => { + "Current session identifier (omitted until session starts)" + } + } + } + + /// Returns an example rendering of this item for the preview. + /// + /// These are placeholder values used to show users what each item looks + /// like in the status line before they confirm their selection. + pub(crate) fn render(&self) -> &'static str { + match self { + StatusLineItem::ModelName => "gpt-5.2-codex", + StatusLineItem::ModelWithReasoning => "gpt-5.2-codex medium", + StatusLineItem::CurrentDir => "~/project/path", + StatusLineItem::ProjectRoot => "~/project", + StatusLineItem::GitBranch => "feat/awesome-feature", + StatusLineItem::ContextRemaining => "18% left", + StatusLineItem::ContextUsed => "82% used", + StatusLineItem::FiveHourLimit => "5h 100%", + StatusLineItem::WeeklyLimit => "weekly 98%", + StatusLineItem::CodexVersion => "v0.93.0", + StatusLineItem::ContextWindowSize => "258K window", + StatusLineItem::UsedTokens => "27.3K used", + StatusLineItem::TotalInputTokens => "17,588 in", + StatusLineItem::TotalOutputTokens => "265 out", + StatusLineItem::SessionId => "019c19bd-ceb6-73b0-adc8-8ec0397b85cf", + } + } +} + +/// Interactive view for configuring which items appear in the status line. +/// +/// Wraps a [`MultiSelectPicker`] with status-line-specific behavior: +/// - Pre-populates items from current configuration +/// - Shows a live preview of the configured status line +/// - Emits [`AppEvent::StatusLineSetup`] on confirmation +/// - Emits [`AppEvent::StatusLineSetupCancelled`] on cancellation +pub(crate) struct StatusLineSetupView { + /// The underlying multi-select picker widget. + picker: MultiSelectPicker, +} + +impl StatusLineSetupView { + /// Creates a new status line setup view. + /// + /// # Arguments + /// + /// * `status_line_items` - Currently configured item IDs (in display order), + /// or `None` to start with all items disabled + /// * `app_event_tx` - Event sender for dispatching configuration changes + /// + /// Items from `status_line_items` are shown first (in order) and marked as + /// enabled. Remaining items are appended and marked as disabled. + pub(crate) fn new(status_line_items: Option<&[String]>, app_event_tx: AppEventSender) -> Self { + let mut used_ids = HashSet::new(); + let mut items = Vec::new(); + + if let Some(selected_items) = status_line_items.as_ref() { + for id in *selected_items { + let Ok(item) = id.parse::() else { + continue; + }; + let item_id = item.to_string(); + if !used_ids.insert(item_id.clone()) { + continue; + } + items.push(Self::status_line_select_item(item, true)); + } + } + + for item in StatusLineItem::iter() { + let item_id = item.to_string(); + if used_ids.contains(&item_id) { + continue; + } + items.push(Self::status_line_select_item(item, false)); + } + + Self { + picker: MultiSelectPicker::builder( + "Configure Status Line".to_string(), + Some("Select which items to display in the status line.".to_string()), + app_event_tx, + ) + .instructions(vec![ + "Use ↑↓ to navigate, ←→ to move, space to select, enter to confirm, esc to cancel." + .into(), + ]) + .items(items) + .enable_ordering() + .on_preview(|items| { + let preview = items + .iter() + .filter(|item| item.enabled) + .filter_map(|item| item.id.parse::().ok()) + .map(|item| item.render()) + .collect::>() + .join(" · "); + if preview.is_empty() { + None + } else { + Some(Line::from(preview)) + } + }) + .on_confirm(|ids, app_event| { + let items = ids + .iter() + .map(|id| id.parse::()) + .collect::, _>>() + .unwrap_or_default(); + app_event.send(AppEvent::StatusLineSetup { items }); + }) + .on_cancel(|app_event| { + app_event.send(AppEvent::StatusLineSetupCancelled); + }) + .build(), + } + } + + /// Converts a [`StatusLineItem`] into a [`MultiSelectItem`] for the picker. + fn status_line_select_item(item: StatusLineItem, enabled: bool) -> MultiSelectItem { + MultiSelectItem { + id: item.to_string(), + name: item.to_string(), + description: Some(item.description().to_string()), + enabled, + } + } +} + +impl BottomPaneView for StatusLineSetupView { + fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) { + self.picker.handle_key_event(key_event); + } + + fn is_complete(&self) -> bool { + self.picker.complete + } + + fn on_ctrl_c(&mut self) -> CancellationEvent { + self.picker.close(); + CancellationEvent::Handled + } +} + +impl Renderable for StatusLineSetupView { + fn render(&self, area: Rect, buf: &mut Buffer) { + self.picker.render(area, buf) + } + + fn desired_height(&self, width: u16) -> u16 { + self.picker.desired_height(width) + } +} diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index bbe6321a3b..4b260c67eb 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -26,19 +26,30 @@ use std::collections::VecDeque; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; use std::time::Duration; use std::time::Instant; +use crate::bottom_pane::StatusLineItem; +use crate::bottom_pane::StatusLineSetupView; +use crate::status::RateLimitWindowDisplay; +use crate::status::format_directory_display; +use crate::status::format_tokens_compact; +use crate::text_formatting::proper_join; use crate::version::CODEX_CLI_VERSION; +use codex_app_server_protocol::ConfigLayerSource; use codex_backend_client::Client as BackendClient; use codex_chatgpt::connectors; use codex_core::config::Config; use codex_core::config::ConstraintResult; use codex_core::config::types::Notifications; +use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::features::FEATURES; use codex_core::features::Feature; use codex_core::find_thread_name_by_id; use codex_core::git_info::current_branch_name; +use codex_core::git_info::get_git_repo_root; use codex_core::git_info::local_git_branches; use codex_core::models_manager::manager::ModelsManager; use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; @@ -390,6 +401,8 @@ pub(crate) struct ChatWidgetInit { pub(crate) is_first_run: bool, pub(crate) feedback_audience: FeedbackAudience, pub(crate) model: Option, + // Shared latch so we only warn once about invalid status-line item IDs. + pub(crate) status_line_invalid_items_warned: Arc, pub(crate) otel_manager: OtelManager, } @@ -581,6 +594,18 @@ pub(crate) struct ChatWidget { feedback_audience: FeedbackAudience, // Current session rollout path (if known) current_rollout_path: Option, + // Current working directory (if known) + current_cwd: Option, + // Shared latch so we only warn once about invalid status-line item IDs. + status_line_invalid_items_warned: Arc, + // Cached git branch name for the status line (None if unknown). + status_line_branch: Option, + // CWD used to resolve the cached branch; change resets branch state. + status_line_branch_cwd: Option, + // True while an async branch lookup is in flight. + status_line_branch_pending: bool, + // True once we've attempted a branch lookup for the current CWD. + status_line_branch_lookup_complete: bool, external_editor_state: ExternalEditorState, } @@ -792,6 +817,120 @@ impl ChatWidget { self.set_status(header, None); } + /// Sets the currently rendered footer status-line value and schedules a redraw. + pub(crate) fn set_status_line(&mut self, status_line: Option>) { + self.bottom_pane.set_status_line(status_line); + self.request_redraw(); + } + + /// Recomputes footer status-line content from config and current runtime state. + /// + /// This method is the status-line orchestrator: it parses configured item identifiers, + /// warns once per session about invalid items, updates whether status-line mode is enabled, + /// schedules async git-branch lookup when needed, and renders only values that are currently + /// available. + /// + /// The omission behavior is intentional. If selected items are unavailable (for example before + /// a session id exists or before branch lookup completes), those items are skipped without + /// placeholders so the line remains compact and stable. + pub(crate) fn refresh_status_line(&mut self) { + let (items, invalid_items) = self.status_line_items_with_invalids(); + if self.thread_id.is_some() + && !invalid_items.is_empty() + && self + .status_line_invalid_items_warned + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + let label = if invalid_items.len() == 1 { + "item" + } else { + "items" + }; + let message = format!( + "Ignored invalid status line {label}: {}.", + proper_join(invalid_items.as_slice()) + ); + self.on_warning(message); + } + if !items.contains(&StatusLineItem::GitBranch) { + self.status_line_branch = None; + self.status_line_branch_pending = false; + self.status_line_branch_lookup_complete = false; + } + let enabled = !items.is_empty(); + self.bottom_pane.set_status_line_enabled(enabled); + if !enabled { + self.set_status_line(None); + return; + } + + let cwd = self.status_line_cwd().to_path_buf(); + self.sync_status_line_branch_state(&cwd); + + if items.contains(&StatusLineItem::GitBranch) && !self.status_line_branch_lookup_complete { + self.request_status_line_branch(cwd); + } + + let mut parts = Vec::new(); + for item in items { + if let Some(value) = self.status_line_value_for_item(&item) { + parts.push(value); + } + } + + let line = if parts.is_empty() { + None + } else { + Some(Line::from(parts.join(" · "))) + }; + self.set_status_line(line); + } + + /// Records that status-line setup was canceled. + /// + /// Cancellation is intentionally side-effect free for config state; the existing configuration + /// remains active and no persistence is attempted. + pub(crate) fn cancel_status_line_setup(&self) { + tracing::info!("Status line setup canceled by user"); + } + + /// Applies status-line item selection from the setup view to in-memory config. + /// + /// An empty selection is normalized to `None` so the status line is fully disabled and the + /// behavior matches an unset `tui.status_line` config value. + pub(crate) fn setup_status_line(&mut self, items: Vec) { + tracing::info!("status line setup confirmed with items: {items:#?}"); + let ids = items.iter().map(ToString::to_string).collect::>(); + self.config.tui_status_line = if ids.is_empty() { None } else { Some(ids) }; + self.refresh_status_line(); + } + + /// Stores async git-branch lookup results for the current status-line cwd. + /// + /// Results are dropped when they target an out-of-date cwd to avoid rendering stale branch + /// names after directory changes. + pub(crate) fn set_status_line_branch(&mut self, cwd: PathBuf, branch: Option) { + if self.status_line_branch_cwd.as_ref() != Some(&cwd) { + self.status_line_branch_pending = false; + return; + } + self.status_line_branch = branch; + self.status_line_branch_pending = false; + self.status_line_branch_lookup_complete = true; + } + + /// Forces a new git-branch lookup when `GitBranch` is part of the configured status line. + fn request_status_line_branch_refresh(&mut self) { + let (items, _) = self.status_line_items_with_invalids(); + if items.is_empty() || !items.contains(&StatusLineItem::GitBranch) { + return; + } + let cwd = self.status_line_cwd().to_path_buf(); + self.sync_status_line_branch_state(&cwd); + self.request_status_line_branch(cwd); + } + fn restore_retry_status_header_if_present(&mut self) { if let Some(header) = self.retry_status_header.take() { self.set_status_header(header); @@ -808,6 +947,7 @@ impl ChatWidget { self.thread_name = event.thread_name.clone(); self.forked_from = event.forked_from_id; self.current_rollout_path = event.rollout_path.clone(); + self.current_cwd = Some(event.cwd.clone()); let initial_messages = event.initial_messages.clone(); let forked_from_id = event.forked_from_id; let model_for_header = event.model.clone(); @@ -1114,6 +1254,7 @@ impl ChatWidget { } self.needs_final_message_separator = false; self.had_work_activity = false; + self.request_status_line_branch_refresh(); } // Mark task stopped and request redraw now that all content is in history. self.agent_turn_running = false; @@ -1327,6 +1468,7 @@ impl ChatWidget { } else { self.rate_limit_snapshot = None; } + self.refresh_status_line(); } /// Finalize any active exec as failed and stop/clear agent-turn UI state. /// @@ -1346,6 +1488,7 @@ impl ChatWidget { self.adaptive_chunking.reset(); self.stream_controller = None; self.plan_stream_controller = None; + self.request_status_line_branch_refresh(); self.maybe_show_pending_rate_limit_prompt(); } @@ -1851,6 +1994,7 @@ impl ChatWidget { fn on_turn_diff(&mut self, unified_diff: String) { debug!("TurnDiffEvent: {unified_diff}"); + self.refresh_status_line(); } fn on_deprecation_notice(&mut self, event: DeprecationNoticeEvent) { @@ -2284,6 +2428,7 @@ impl ChatWidget { is_first_run, feedback_audience, model, + status_line_invalid_items_warned, otel_manager, } = common; let model = model.filter(|m| !m.trim().is_empty()); @@ -2316,6 +2461,7 @@ impl ChatWidget { let active_cell = Some(Self::placeholder_session_header_cell(&config)); + let current_cwd = Some(config.cwd.clone()); let mut widget = Self { app_event_tx: app_event_tx.clone(), frame_requester: frame_requester.clone(), @@ -2387,6 +2533,12 @@ impl ChatWidget { feedback, feedback_audience, current_rollout_path: None, + current_cwd, + status_line_invalid_items_warned, + status_line_branch: None, + status_line_branch_cwd: None, + status_line_branch_pending: false, + status_line_branch_lookup_complete: false, external_editor_state: ExternalEditorState::Closed, }; @@ -2394,6 +2546,13 @@ impl ChatWidget { widget .bottom_pane .set_steer_enabled(widget.config.features.enabled(Feature::Steer)); + widget.bottom_pane.set_status_line_enabled( + widget + .config + .tui_status_line + .as_ref() + .is_some_and(|items| !items.is_empty()), + ); widget.bottom_pane.set_collaboration_modes_enabled( widget.config.features.enabled(Feature::CollaborationModes), ); @@ -2431,6 +2590,7 @@ impl ChatWidget { is_first_run, feedback_audience, model, + status_line_invalid_items_warned, otel_manager, } = common; let model = model.filter(|m| !m.trim().is_empty()); @@ -2461,6 +2621,7 @@ impl ChatWidget { }; let active_cell = Some(Self::placeholder_session_header_cell(&config)); + let current_cwd = Some(config.cwd.clone()); let mut widget = Self { app_event_tx: app_event_tx.clone(), @@ -2533,6 +2694,12 @@ impl ChatWidget { feedback, feedback_audience, current_rollout_path: None, + current_cwd, + status_line_invalid_items_warned, + status_line_branch: None, + status_line_branch_cwd: None, + status_line_branch_pending: false, + status_line_branch_lookup_complete: false, external_editor_state: ExternalEditorState::Closed, }; @@ -2540,6 +2707,13 @@ impl ChatWidget { widget .bottom_pane .set_steer_enabled(widget.config.features.enabled(Feature::Steer)); + widget.bottom_pane.set_status_line_enabled( + widget + .config + .tui_status_line + .as_ref() + .is_some_and(|items| !items.is_empty()), + ); widget.bottom_pane.set_collaboration_modes_enabled( widget.config.features.enabled(Feature::CollaborationModes), ); @@ -2563,10 +2737,11 @@ impl ChatWidget { auth_manager, models_manager, feedback, + is_first_run: _, feedback_audience, model, + status_line_invalid_items_warned, otel_manager, - .. } = common; let model = model.filter(|m| !m.trim().is_empty()); let mut rng = rand::rng(); @@ -2583,6 +2758,7 @@ impl ChatWidget { .and_then(|mask| mask.model.clone()) .unwrap_or(header_model); + let current_cwd = Some(session_configured.cwd.clone()); let codex_op_tx = spawn_agent_from_existing(conversation, session_configured, app_event_tx.clone()); @@ -2668,6 +2844,12 @@ impl ChatWidget { feedback, feedback_audience, current_rollout_path: None, + current_cwd, + status_line_invalid_items_warned, + status_line_branch: None, + status_line_branch_cwd: None, + status_line_branch_pending: false, + status_line_branch_lookup_complete: false, external_editor_state: ExternalEditorState::Closed, }; @@ -2675,6 +2857,13 @@ impl ChatWidget { widget .bottom_pane .set_steer_enabled(widget.config.features.enabled(Feature::Steer)); + widget.bottom_pane.set_status_line_enabled( + widget + .config + .tui_status_line + .as_ref() + .is_some_and(|items| !items.is_empty()), + ); widget.bottom_pane.set_collaboration_modes_enabled( widget.config.features.enabled(Feature::CollaborationModes), ); @@ -3067,6 +3256,9 @@ impl ChatWidget { SlashCommand::DebugConfig => { self.add_debug_config_output(); } + SlashCommand::Statusline => { + self.open_status_line_setup(); + } SlashCommand::Ps => { self.add_ps_output(); } @@ -3846,6 +4038,229 @@ impl ChatWidget { self.add_to_history(crate::debug_config::new_debug_config_output(&self.config)); } + fn open_status_line_setup(&mut self) { + let view = StatusLineSetupView::new( + self.config.tui_status_line.as_deref(), + self.app_event_tx.clone(), + ); + self.bottom_pane.show_view(Box::new(view)); + } + + /// Parses configured status-line ids into known items and collects unknown ids. + /// + /// Unknown ids are deduplicated in insertion order for warning messages. + fn status_line_items_with_invalids(&self) -> (Vec, Vec) { + let mut invalid = Vec::new(); + let mut invalid_seen = HashSet::new(); + let mut items = Vec::new(); + let Some(config_items) = self.config.tui_status_line.as_ref() else { + return (items, invalid); + }; + for id in config_items { + match id.parse::() { + Ok(item) => items.push(item), + Err(_) => { + if invalid_seen.insert(id.clone()) { + invalid.push(format!(r#""{id}""#)); + } + } + } + } + (items, invalid) + } + + fn status_line_cwd(&self) -> &Path { + self.current_cwd.as_ref().unwrap_or(&self.config.cwd) + } + + fn status_line_project_root(&self) -> Option { + let cwd = self.status_line_cwd(); + if let Some(repo_root) = get_git_repo_root(cwd) { + return Some(repo_root); + } + + self.config + .config_layer_stack + .get_layers(ConfigLayerStackOrdering::LowestPrecedenceFirst, true) + .iter() + .find_map(|layer| match &layer.name { + ConfigLayerSource::Project { dot_codex_folder } => { + dot_codex_folder.as_path().parent().map(Path::to_path_buf) + } + _ => None, + }) + } + + fn status_line_project_root_name(&self) -> Option { + self.status_line_project_root().map(|root| { + root.file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_else(|| format_directory_display(&root, None)) + }) + } + + /// Resets git-branch cache state when the status-line cwd changes. + /// + /// The branch cache is keyed by cwd because branch lookup is performed relative to that path. + /// Keeping stale branch values across cwd changes would surface incorrect repository context. + fn sync_status_line_branch_state(&mut self, cwd: &Path) { + if self + .status_line_branch_cwd + .as_ref() + .is_some_and(|path| path == cwd) + { + return; + } + self.status_line_branch_cwd = Some(cwd.to_path_buf()); + self.status_line_branch = None; + self.status_line_branch_pending = false; + self.status_line_branch_lookup_complete = false; + } + + /// Starts an async git-branch lookup unless one is already running. + /// + /// The resulting `StatusLineBranchUpdated` event carries the lookup cwd so callers can reject + /// stale completions after directory changes. + fn request_status_line_branch(&mut self, cwd: PathBuf) { + if self.status_line_branch_pending { + return; + } + self.status_line_branch_pending = true; + let tx = self.app_event_tx.clone(); + tokio::spawn(async move { + let branch = current_branch_name(&cwd).await; + tx.send(AppEvent::StatusLineBranchUpdated { cwd, branch }); + }); + } + + /// Resolves a display string for one configured status-line item. + /// + /// Returning `None` means "omit this item for now", not "configuration error". Callers rely on + /// this to keep partially available status lines readable while waiting for session, token, or + /// git metadata. + fn status_line_value_for_item(&self, item: &StatusLineItem) -> Option { + match item { + StatusLineItem::ModelName => Some(self.model_display_name().to_string()), + StatusLineItem::ModelWithReasoning => { + let label = + Self::status_line_reasoning_effort_label(self.effective_reasoning_effort()); + Some(format!("{} {label}", self.model_display_name())) + } + StatusLineItem::CurrentDir => { + Some(format_directory_display(self.status_line_cwd(), None)) + } + StatusLineItem::ProjectRoot => self.status_line_project_root_name(), + StatusLineItem::GitBranch => self.status_line_branch.clone(), + StatusLineItem::UsedTokens => { + let usage = self.status_line_total_usage(); + let total = usage.tokens_in_context_window(); + if total <= 0 { + None + } else { + Some(format!("{} used", format_tokens_compact(total))) + } + } + StatusLineItem::ContextRemaining => self + .status_line_context_remaining_percent() + .map(|remaining| format!("{remaining}% left")), + StatusLineItem::ContextUsed => self + .status_line_context_used_percent() + .map(|used| format!("{used}% used")), + StatusLineItem::FiveHourLimit => { + let window = self + .rate_limit_snapshot + .as_ref() + .and_then(|s| s.primary.as_ref()); + let label = window + .and_then(|window| window.window_minutes) + .map(get_limits_duration) + .unwrap_or_else(|| "5h".to_string()); + self.status_line_limit_display(window, &label) + } + StatusLineItem::WeeklyLimit => { + let window = self + .rate_limit_snapshot + .as_ref() + .and_then(|s| s.secondary.as_ref()); + let label = window + .and_then(|window| window.window_minutes) + .map(get_limits_duration) + .unwrap_or_else(|| "weekly".to_string()); + self.status_line_limit_display(window, &label) + } + StatusLineItem::CodexVersion => Some(CODEX_CLI_VERSION.to_string()), + StatusLineItem::ContextWindowSize => self + .status_line_context_window_size() + .map(|cws| format!("{} window", format_tokens_compact(cws))), + StatusLineItem::TotalInputTokens => Some(format!( + "{} in", + format_tokens_compact(self.status_line_total_usage().input_tokens) + )), + StatusLineItem::TotalOutputTokens => Some(format!( + "{} out", + format_tokens_compact(self.status_line_total_usage().output_tokens) + )), + StatusLineItem::SessionId => self.thread_id.map(|id| id.to_string()), + } + } + + fn status_line_context_window_size(&self) -> Option { + self.token_info + .as_ref() + .and_then(|info| info.model_context_window) + .or(self.config.model_context_window) + } + + fn status_line_context_remaining_percent(&self) -> Option { + let Some(context_window) = self.status_line_context_window_size() else { + return Some(100); + }; + let default_usage = TokenUsage::default(); + let usage = self + .token_info + .as_ref() + .map(|info| &info.last_token_usage) + .unwrap_or(&default_usage); + Some( + usage + .percent_of_context_window_remaining(context_window) + .clamp(0, 100), + ) + } + + fn status_line_context_used_percent(&self) -> Option { + let remaining = self.status_line_context_remaining_percent().unwrap_or(100); + Some((100 - remaining).clamp(0, 100)) + } + + fn status_line_total_usage(&self) -> TokenUsage { + self.token_info + .as_ref() + .map(|info| info.total_token_usage.clone()) + .unwrap_or_default() + } + + fn status_line_limit_display( + &self, + window: Option<&RateLimitWindowDisplay>, + label: &str, + ) -> Option { + let window = window?; + let remaining = (100.0f64 - window.used_percent).clamp(0.0f64, 100.0f64); + Some(format!("{label} {remaining:.0}%")) + } + + fn status_line_reasoning_effort_label(effort: Option) -> &'static str { + match effort { + Some(ReasoningEffortConfig::Minimal) => "minimal", + Some(ReasoningEffortConfig::Low) => "low", + Some(ReasoningEffortConfig::Medium) => "medium", + Some(ReasoningEffortConfig::High) => "high", + Some(ReasoningEffortConfig::XHigh) => "xhigh", + None | Some(ReasoningEffortConfig::None) => "default", + } + } + pub(crate) fn add_ps_output(&mut self) { let processes = self .unified_exec_processes diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 90b275b157..f49493b6a1 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -830,6 +830,7 @@ async fn helpers_are_available_and_do_not_panic() { is_first_run: true, feedback_audience: FeedbackAudience::External, model: Some(resolved_model), + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), otel_manager, }; let mut w = ChatWidget::new(init, thread_manager); @@ -958,6 +959,12 @@ async fn make_chatwidget_manual( feedback: codex_feedback::CodexFeedback::new(), feedback_audience: FeedbackAudience::External, current_rollout_path: None, + current_cwd: None, + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), + status_line_branch: None, + status_line_branch_cwd: None, + status_line_branch_pending: false, + status_line_branch_lookup_complete: false, external_editor_state: ExternalEditorState::Closed, }; widget.set_model(&resolved_model); @@ -2637,6 +2644,7 @@ async fn collaboration_modes_defaults_to_code_on_startup() { is_first_run: true, feedback_audience: FeedbackAudience::External, model: Some(resolved_model.clone()), + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), otel_manager, }; @@ -2682,6 +2690,7 @@ async fn experimental_mode_plan_applies_on_startup() { is_first_run: true, feedback_audience: FeedbackAudience::External, model: Some(resolved_model.clone()), + status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)), otel_manager, }; @@ -4907,6 +4916,83 @@ async fn warning_event_adds_warning_history_cell() { ); } +#[tokio::test] +async fn status_line_invalid_items_warn_once() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + chat.config.tui_status_line = Some(vec![ + "model_name".to_string(), + "bogus_item".to_string(), + "lines_changed".to_string(), + "bogus_item".to_string(), + ]); + chat.thread_id = Some(ThreadId::new()); + + chat.refresh_status_line(); + let cells = drain_insert_history(&mut rx); + assert_eq!(cells.len(), 1, "expected one warning history cell"); + let rendered = lines_to_single_string(&cells[0]); + assert!( + rendered.contains("bogus_item"), + "warning cell missing invalid item content: {rendered}" + ); + + chat.refresh_status_line(); + let cells = drain_insert_history(&mut rx); + assert!( + cells.is_empty(), + "expected invalid status line warning to emit only once" + ); +} + +#[tokio::test] +async fn status_line_branch_state_resets_when_git_branch_disabled() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; + chat.status_line_branch = Some("main".to_string()); + chat.status_line_branch_pending = true; + chat.status_line_branch_lookup_complete = true; + chat.config.tui_status_line = Some(vec!["model_name".to_string()]); + + chat.refresh_status_line(); + + assert_eq!(chat.status_line_branch, None); + assert!(!chat.status_line_branch_pending); + assert!(!chat.status_line_branch_lookup_complete); +} + +#[tokio::test] +async fn status_line_branch_refreshes_after_turn_complete() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; + chat.config.tui_status_line = Some(vec!["git-branch".to_string()]); + chat.status_line_branch_lookup_complete = true; + chat.status_line_branch_pending = false; + + chat.handle_codex_event(Event { + id: "turn-1".into(), + msg: EventMsg::TurnComplete(TurnCompleteEvent { + last_agent_message: None, + }), + }); + + assert!(chat.status_line_branch_pending); +} + +#[tokio::test] +async fn status_line_branch_refreshes_after_interrupt() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; + chat.config.tui_status_line = Some(vec!["git-branch".to_string()]); + chat.status_line_branch_lookup_complete = true; + chat.status_line_branch_pending = false; + + chat.handle_codex_event(Event { + id: "turn-1".into(), + msg: EventMsg::TurnAborted(codex_core::protocol::TurnAbortedEvent { + reason: TurnAbortReason::Interrupted, + }), + }); + + assert!(chat.status_line_branch_pending); +} + #[tokio::test] async fn stream_recovery_restores_previous_status_header() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; diff --git a/codex-rs/tui/src/diff_render.rs b/codex-rs/tui/src/diff_render.rs index 505ebc8edc..9683df53ba 100644 --- a/codex-rs/tui/src/diff_render.rs +++ b/codex-rs/tui/src/diff_render.rs @@ -325,7 +325,7 @@ pub(crate) fn display_path_for(path: &Path, cwd: &Path) -> String { chosen.display().to_string() } -fn calculate_add_remove_from_diff(diff: &str) -> (usize, usize) { +pub(crate) fn calculate_add_remove_from_diff(diff: &str) -> (usize, usize) { if let Ok(patch) = diffy::Patch::from_str(diff) { patch .hunks() diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index e4ac5dcacf..6bb41b4696 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -34,6 +34,7 @@ pub enum SlashCommand { Mention, Status, DebugConfig, + Statusline, Mcp, Apps, Logout, @@ -65,6 +66,7 @@ impl SlashCommand { SlashCommand::Skills => "use skills to improve how Codex performs specific tasks", SlashCommand::Status => "show current session configuration and token usage", SlashCommand::DebugConfig => "show config layers and requirement sources for debugging", + SlashCommand::Statusline => "configure which items appear in the status line", SlashCommand::Ps => "list background terminals", SlashCommand::Model => "choose what model and reasoning effort to use", SlashCommand::Personality => "choose a communication style for Codex", @@ -131,6 +133,7 @@ impl SlashCommand { SlashCommand::TestApproval => true, SlashCommand::Collab => true, SlashCommand::Agent => true, + SlashCommand::Statusline => false, } } diff --git a/codex-rs/tui/src/status/mod.rs b/codex-rs/tui/src/status/mod.rs index 89c8daefdc..4f46af1cbc 100644 --- a/codex-rs/tui/src/status/mod.rs +++ b/codex-rs/tui/src/status/mod.rs @@ -1,3 +1,11 @@ +//! Status output formatting and display adapters for the TUI. +//! +//! This module turns protocol-level snapshots into stable display structures used by `/status` +//! output and footer/status-line helpers, while keeping rendering concerns out of transport-facing +//! code. +//! +//! `rate_limits` is the main integration point for status-line usage-limit items: it converts raw +//! window snapshots into local-time labels and classifies data as available, stale, or missing. mod account; mod card; mod format; @@ -5,8 +13,10 @@ mod helpers; mod rate_limits; pub(crate) use card::new_status_output; +pub(crate) use helpers::format_directory_display; pub(crate) use helpers::format_tokens_compact; pub(crate) use rate_limits::RateLimitSnapshotDisplay; +pub(crate) use rate_limits::RateLimitWindowDisplay; pub(crate) use rate_limits::rate_limit_snapshot_display; #[cfg(test)] diff --git a/codex-rs/tui/src/status/rate_limits.rs b/codex-rs/tui/src/status/rate_limits.rs index 3fae3ac295..830fe158b2 100644 --- a/codex-rs/tui/src/status/rate_limits.rs +++ b/codex-rs/tui/src/status/rate_limits.rs @@ -1,3 +1,10 @@ +//! Rate-limit and credits display shaping for status surfaces. +//! +//! This module maps `RateLimitSnapshot` protocol payloads into display-oriented rows that the TUI +//! can render in `/status` and status-line contexts without duplicating formatting logic. +//! +//! The key contract is that time-sensitive values are interpreted relative to a caller-provided +//! capture timestamp so stale detection and reset labels remain coherent for a given draw cycle. use crate::chatwidget::get_limits_duration; use crate::text_formatting::capitalize_first; @@ -16,42 +23,58 @@ const STATUS_LIMIT_BAR_EMPTY: &str = "░"; #[derive(Debug, Clone)] pub(crate) struct StatusRateLimitRow { + /// Human-readable row label, such as `"5h limit"` or `"Credits"`. pub label: String, + /// Value payload for the row. pub value: StatusRateLimitValue, } +/// Display value variants for a single rate-limit row. #[derive(Debug, Clone)] pub(crate) enum StatusRateLimitValue { + /// Percent-based usage window with optional reset timestamp text. Window { + /// Percent of the window that has been consumed. percent_used: f64, + /// Localized reset string, or `None` when unknown. resets_at: Option, }, + /// Plain text value used for non-window rows. Text(String), } +/// Availability state for rate-limit data shown in status output. #[derive(Debug, Clone)] pub(crate) enum StatusRateLimitData { + /// Snapshot data is recent enough for normal rendering. Available(Vec), + /// Snapshot data exists but is older than the staleness threshold. Stale(Vec), + /// No snapshot data is currently available. Missing, } +/// Maximum age before a snapshot is considered stale in status output. pub(crate) const RATE_LIMIT_STALE_THRESHOLD_MINUTES: i64 = 15; +/// Display-friendly representation of one usage window from a snapshot. #[derive(Debug, Clone)] pub(crate) struct RateLimitWindowDisplay { + /// Percent used for the window. pub used_percent: f64, + /// Human-readable local reset time. pub resets_at: Option, + /// Window length in minutes when provided by the server. pub window_minutes: Option, } impl RateLimitWindowDisplay { fn from_window(window: &RateLimitWindow, captured_at: DateTime) -> Self { - let resets_at = window + let resets_at_utc = window .resets_at .and_then(|seconds| DateTime::::from_timestamp(seconds, 0)) - .map(|dt| dt.with_timezone(&Local)) - .map(|dt| format_reset_timestamp(dt, captured_at)); + .map(|dt| dt.with_timezone(&Local)); + let resets_at = resets_at_utc.map(|dt| format_reset_timestamp(dt, captured_at)); Self { used_percent: window.used_percent, @@ -63,19 +86,31 @@ impl RateLimitWindowDisplay { #[derive(Debug, Clone)] pub(crate) struct RateLimitSnapshotDisplay { + /// Local timestamp representing when this display snapshot was captured. pub captured_at: DateTime, + /// Primary usage window (typically short duration). pub primary: Option, + /// Secondary usage window (typically weekly). pub secondary: Option, + /// Optional credits metadata when available. pub credits: Option, } +/// Display-ready credits state extracted from protocol snapshots. #[derive(Debug, Clone)] pub(crate) struct CreditsSnapshotDisplay { + /// Whether credits tracking is enabled for the account. pub has_credits: bool, + /// Whether the account has unlimited credits. pub unlimited: bool, + /// Raw balance text as provided by the backend. pub balance: Option, } +/// Converts a protocol snapshot into UI-friendly display data. +/// +/// Pass the timestamp from the same observation point as `snapshot`; supplying a significantly +/// older or newer `captured_at` can produce misleading reset labels and stale classification. pub(crate) fn rate_limit_snapshot_display( snapshot: &RateLimitSnapshot, captured_at: DateTime, @@ -104,6 +139,10 @@ impl From<&CoreCreditsSnapshot> for CreditsSnapshotDisplay { } } +/// Builds display rows from a snapshot and marks stale data by capture age. +/// +/// Callers should pass `Local::now()` for `now` at render time; using a cached timestamp can make +/// fresh data appear stale or prevent stale warnings from appearing. pub(crate) fn compose_rate_limit_data( snapshot: Option<&RateLimitSnapshotDisplay>, now: DateTime, @@ -163,6 +202,10 @@ pub(crate) fn compose_rate_limit_data( } } +/// Renders a fixed-width progress bar from remaining percentage. +/// +/// This function expects a remaining value in the `0..=100` range and clamps out-of-range input. +/// Passing a used percentage by mistake will invert the bar and mislead users. pub(crate) fn render_status_limit_progress_bar(percent_remaining: f64) -> String { let ratio = (percent_remaining / 100.0).clamp(0.0, 1.0); let filled = (ratio * STATUS_LIMIT_BAR_SEGMENTS as f64).round() as usize; @@ -175,6 +218,7 @@ pub(crate) fn render_status_limit_progress_bar(percent_remaining: f64) -> String ) } +/// Formats a compact textual summary from remaining percentage. pub(crate) fn format_status_limit_summary(percent_remaining: f64) -> String { format!("{percent_remaining:.0}% left") } diff --git a/codex-rs/tui/src/text_formatting.rs b/codex-rs/tui/src/text_formatting.rs index f747b2fef2..4869645dc0 100644 --- a/codex-rs/tui/src/text_formatting.rs +++ b/codex-rs/tui/src/text_formatting.rs @@ -327,6 +327,33 @@ pub(crate) fn center_truncate_path(path: &str, max_width: usize) -> String { front_truncate(path, max_width) } +/// Join a list of strings with proper English punctuation. +/// Examples: +/// - [] -> "" +/// - ["apple"] -> "apple" +/// - ["apple", "banana"] -> "apple and banana" +/// - ["apple", "banana", "cherry"] -> "apple, banana and cherry" +pub(crate) fn proper_join>(items: &[T]) -> String { + match items.len() { + 0 => String::new(), + 1 => items[0].as_ref().to_string(), + 2 => format!("{} and {}", items[0].as_ref(), items[1].as_ref()), + _ => { + let last = items[items.len() - 1].as_ref(); + let mut result = String::new(); + + for (i, item) in items.iter().take(items.len() - 1).enumerate() { + if i > 0 { + result.push_str(", "); + } + result.push_str(item.as_ref()); + } + + format!("{result} and {last}") + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -534,4 +561,20 @@ mod tests { assert_eq!(format_json_compact("null").unwrap(), "null"); assert_eq!(format_json_compact(r#""string""#).unwrap(), r#""string""#); } + + #[test] + fn test_proper_join() { + let empty: Vec = vec![]; + assert_eq!(proper_join(&empty), ""); + assert_eq!(proper_join(&["apple"]), "apple"); + assert_eq!(proper_join(&["apple", "banana"]), "apple and banana"); + assert_eq!( + proper_join(&["apple", "banana", "cherry"]), + "apple, banana and cherry" + ); + assert_eq!( + proper_join(&["apple", "banana", "cherry", "date"]), + "apple, banana, cherry and date" + ); + } } From 22545bf2064ce32d6056957bebd6541a3c8131eb Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Thu, 5 Feb 2026 14:08:31 -0300 Subject: [PATCH 68/90] feat(tui): add sortable resume picker with created/updated timestamp toggle (#10752) ## Summary - Add sorting support to the resume session picker with Tab key toggle - Sessions can now be sorted by either creation time or last updated time - Display the current sort mode in the picker header - Default to sorting by creation time (most recent first) ## Changes - Add `sort_key` field to `PickerState` to track current sort order - Pass sort key to `RolloutRecorder::list_threads()` for proper backend sorting - Add Tab key handler to toggle between `CreatedAt` and `UpdatedAt` sorting - Show current sort mode ("Created at" / "Updated at") in header - Add "Tab to toggle sort" keyboard hint - Intelligently hide secondary date column when terminal is narrow - Reload session list when sort order changes ## Test plan - [x] Unit tests for sort key toggle functionality - [x] Snapshot tests updated for new header format - [x] Test that Tab key triggers reload with new sort key - [x] Test column visibility adapts to narrow terminals --- codex-rs/tui/src/resume_picker.rs | 432 ++++++++++++++++-- ...e_picker__tests__resume_picker_screen.snap | 7 +- ...me_picker__tests__resume_picker_table.snap | 8 +- ...er__tests__resume_picker_thread_names.snap | 7 +- 4 files changed, 411 insertions(+), 43 deletions(-) diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index f35acc07ff..d0fc1146b1 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -83,6 +83,7 @@ struct PageLoadRequest { request_token: usize, search_token: Option, default_provider: String, + sort_key: ThreadSortKey, } type PageLoader = Arc; @@ -96,9 +97,21 @@ enum BackgroundEvent { } /// Interactive session picker that lists recorded rollout files with simple -/// search and pagination. Shows the session name when available, otherwise the -/// first user input as the preview, relative time (e.g., "5 seconds ago"), and -/// the absolute path. +/// search and pagination. +/// +/// The picker displays sessions in a table with timestamp columns (created/updated), +/// git branch, working directory, and conversation preview. Users can toggle +/// between sorting by creation time and last-updated time using the Tab key. +/// +/// Sessions are loaded on-demand via cursor-based pagination. The backend +/// `RolloutRecorder::list_threads` returns pages ordered by the selected sort key, +/// and the picker deduplicates across pages to handle overlapping windows when +/// new sessions appear during pagination. +/// +/// Filtering happens in two layers: +/// 1. Provider and source filtering at the backend (only interactive CLI sessions +/// for the current model provider). +/// 2. Working-directory filtering at the picker (unless `--all` is passed). pub async fn run_resume_picker( tui: &mut Tui, codex_home: &Path, @@ -157,7 +170,7 @@ async fn run_session_picker( &request.codex_home, PAGE_SIZE, request.cursor.as_ref(), - ThreadSortKey::CreatedAt, + request.sort_key, INTERACTIVE_SESSION_SOURCES, Some(provider_filter.as_slice()), request.default_provider.as_str(), @@ -220,6 +233,14 @@ async fn run_session_picker( Ok(SessionSelection::StartFresh) } +/// Returns the human-readable column header for the given sort key. +fn sort_key_label(sort_key: ThreadSortKey) -> &'static str { + match sort_key { + ThreadSortKey::CreatedAt => "Created at", + ThreadSortKey::UpdatedAt => "Updated at", + } +} + /// RAII guard that ensures we leave the alt-screen on scope exit. struct AltScreenGuard<'a> { tui: &'a mut Tui, @@ -257,6 +278,7 @@ struct PickerState { show_all: bool, filter_cwd: Option, action: SessionPickerAction, + sort_key: ThreadSortKey, thread_name_cache: HashMap>, } @@ -373,6 +395,7 @@ impl PickerState { show_all, filter_cwd, action, + sort_key: ThreadSortKey::CreatedAt, thread_name_cache: HashMap::new(), } } @@ -429,6 +452,10 @@ impl PickerState { self.request_frame(); } } + KeyCode::Tab => { + self.toggle_sort_key(); + self.request_frame(); + } KeyCode::Backspace => { let mut new_query = self.query.clone(); new_query.pop(); @@ -456,13 +483,21 @@ impl PickerState { self.all_rows.clear(); self.filtered_rows.clear(); self.seen_paths.clear(); - self.search_state = SearchState::Idle; self.selected = 0; + let search_token = if self.query.is_empty() { + self.search_state = SearchState::Idle; + None + } else { + let token = self.allocate_search_token(); + self.search_state = SearchState::Active { token }; + Some(token) + }; + let request_token = self.allocate_request_token(); self.pagination.loading = LoadingState::Pending(PendingLoad { request_token, - search_token: None, + search_token, }); self.request_frame(); @@ -470,8 +505,9 @@ impl PickerState { codex_home: self.codex_home.clone(), cursor: None, request_token, - search_token: None, + search_token, default_provider: self.default_provider.clone(), + sort_key: self.sort_key, }); } @@ -742,6 +778,7 @@ impl PickerState { request_token, search_token, default_provider: self.default_provider.clone(), + sort_key: self.sort_key, }); } @@ -756,6 +793,19 @@ impl PickerState { self.next_search_token = self.next_search_token.wrapping_add(1); token } + + /// Cycles the sort order between creation time and last-updated time. + /// + /// Triggers a full reload because the backend must re-sort all sessions. + /// The existing `all_rows` are cleared and pagination restarts from the + /// beginning with the new sort key. + fn toggle_sort_key(&mut self) { + self.sort_key = match self.sort_key { + ThreadSortKey::CreatedAt => ThreadSortKey::UpdatedAt, + ThreadSortKey::UpdatedAt => ThreadSortKey::CreatedAt, + }; + self.start_initial_load(); + } } fn rows_from_items(items: Vec) -> Vec { @@ -821,7 +871,15 @@ fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> { .areas(area); // Header - frame.render_widget_ref(Line::from(vec![state.action.title().bold().cyan()]), header); + let header_line: Line = vec![ + state.action.title().bold().cyan(), + " ".into(), + "Sort:".dim(), + " ".into(), + sort_key_label(state.sort_key).magenta(), + ] + .into(); + frame.render_widget_ref(header_line, header); // Search line let q = if state.query.is_empty() { @@ -834,7 +892,7 @@ fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> { let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all); // Column headers and list - render_column_headers(frame, columns, &metrics); + render_column_headers(frame, columns, &metrics, state.sort_key); render_list(frame, list, state, &metrics); // Hint line @@ -849,6 +907,9 @@ fn draw_picker(tui: &mut Tui, state: &PickerState) -> std::io::Result<()> { key_hint::ctrl(KeyCode::Char('c')).into(), " to quit ".dim(), " ".dim(), + key_hint::plain(KeyCode::Tab).into(), + " to toggle sort ".dim(), + " ".dim(), key_hint::plain(KeyCode::Up).into(), "/".dim(), key_hint::plain(KeyCode::Down).into(), @@ -882,11 +943,13 @@ fn render_list( let labels = &metrics.labels; let mut y = area.y; + let visibility = column_visibility(area.width, metrics, state.sort_key); + let max_created_width = metrics.max_created_width; let max_updated_width = metrics.max_updated_width; let max_branch_width = metrics.max_branch_width; let max_cwd_width = metrics.max_cwd_width; - for (idx, (row, (updated_label, branch_label, cwd_label))) in rows[start..end] + for (idx, (row, (created_label, updated_label, branch_label, cwd_label))) in rows[start..end] .iter() .zip(labels[start..end].iter()) .enumerate() @@ -894,12 +957,17 @@ fn render_list( let is_sel = start + idx == state.selected; let marker = if is_sel { "> ".bold() } else { " ".into() }; let marker_width = 2usize; - let updated_span = if max_updated_width == 0 { - None + let created_span = if visibility.show_created { + Some(Span::from(format!("{created_label: 0 { + if visibility.show_created { + preview_width = preview_width.saturating_sub(max_created_width + 2); + } + if visibility.show_updated { preview_width = preview_width.saturating_sub(max_updated_width + 2); } - if max_branch_width > 0 { + if visibility.show_branch { preview_width = preview_width.saturating_sub(max_branch_width + 2); } - if max_cwd_width > 0 { + if visibility.show_cwd { preview_width = preview_width.saturating_sub(max_cwd_width + 2); } - let add_leading_gap = max_updated_width == 0 && max_branch_width == 0 && max_cwd_width == 0; + let add_leading_gap = !visibility.show_created + && !visibility.show_updated + && !visibility.show_branch + && !visibility.show_cwd; if add_leading_gap { preview_width = preview_width.saturating_sub(2); } let preview = truncate_text(row.display_preview(), preview_width); let mut spans: Vec = vec![marker]; + if let Some(created) = created_span { + spans.push(created); + spans.push(" ".into()); + } if let Some(updated) = updated_span { spans.push(updated); spans.push(" ".into()); @@ -1046,26 +1124,44 @@ fn format_updated_label(row: &Row) -> String { } } +fn format_created_label(row: &Row) -> String { + match row.created_at { + Some(created) => human_time_ago(created), + None => "-".to_string(), + } +} + fn render_column_headers( frame: &mut crate::custom_terminal::Frame, area: Rect, metrics: &ColumnMetrics, + sort_key: ThreadSortKey, ) { if area.height == 0 { return; } let mut spans: Vec = vec![" ".into()]; - if metrics.max_updated_width > 0 { + let visibility = column_visibility(area.width, metrics, sort_key); + if visibility.show_created { let label = format!( "{text: 0 { + if visibility.show_branch { let label = format!( "{text: 0 { + if visibility.show_cwd { let label = format!( "{text:, + /// (created_label, updated_label, branch_label, cwd_label) per row. + labels: Vec<(String, String, String, String)>, +} + +/// Determines which columns to render given available terminal width. +/// +/// When the terminal is narrow, only one timestamp column is shown (whichever +/// matches the current sort key). Branch and CWD are hidden if their max +/// widths are zero (no data to show). +#[derive(Debug, PartialEq, Eq)] +struct ColumnVisibility { + show_created: bool, + show_updated: bool, + show_branch: bool, + show_cwd: bool, } fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics { @@ -1114,8 +1229,9 @@ fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics { format!("…{tail}") } - let mut labels: Vec<(String, String, String)> = Vec::with_capacity(rows.len()); - let mut max_updated_width = UnicodeWidthStr::width("Updated"); + let mut labels: Vec<(String, String, String, String)> = Vec::with_capacity(rows.len()); + let mut max_created_width = UnicodeWidthStr::width("Created at"); + let mut max_updated_width = UnicodeWidthStr::width("Updated at"); let mut max_branch_width = UnicodeWidthStr::width("Branch"); let mut max_cwd_width = if include_cwd { UnicodeWidthStr::width("CWD") @@ -1124,6 +1240,7 @@ fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics { }; for row in rows { + let created = format_created_label(row); let updated = format_updated_label(row); let branch_raw = row.git_branch.clone().unwrap_or_default(); let branch = right_elide(&branch_raw, 24); @@ -1137,13 +1254,15 @@ fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics { } else { String::new() }; + max_created_width = max_created_width.max(UnicodeWidthStr::width(created.as_str())); max_updated_width = max_updated_width.max(UnicodeWidthStr::width(updated.as_str())); max_branch_width = max_branch_width.max(UnicodeWidthStr::width(branch.as_str())); max_cwd_width = max_cwd_width.max(UnicodeWidthStr::width(cwd.as_str())); - labels.push((updated, branch, cwd)); + labels.push((created, updated, branch, cwd)); } ColumnMetrics { + max_created_width, max_updated_width, max_branch_width, max_cwd_width, @@ -1151,15 +1270,79 @@ fn calculate_column_metrics(rows: &[Row], include_cwd: bool) -> ColumnMetrics { } } +/// Computes which columns fit in the available width. +/// +/// The algorithm reserves at least `MIN_PREVIEW_WIDTH` characters for the +/// conversation preview. If both timestamp columns don't fit, only the one +/// matching the current sort key is shown. +fn column_visibility( + area_width: u16, + metrics: &ColumnMetrics, + sort_key: ThreadSortKey, +) -> ColumnVisibility { + const MIN_PREVIEW_WIDTH: usize = 10; + + let show_branch = metrics.max_branch_width > 0; + let show_cwd = metrics.max_cwd_width > 0; + + // Calculate remaining width after all optional columns. + let mut preview_width = area_width as usize; + preview_width = preview_width.saturating_sub(2); // marker + if metrics.max_created_width > 0 { + preview_width = preview_width.saturating_sub(metrics.max_created_width + 2); + } + if metrics.max_updated_width > 0 { + preview_width = preview_width.saturating_sub(metrics.max_updated_width + 2); + } + if show_branch { + preview_width = preview_width.saturating_sub(metrics.max_branch_width + 2); + } + if show_cwd { + preview_width = preview_width.saturating_sub(metrics.max_cwd_width + 2); + } + + // If preview would be too narrow, hide the non-active timestamp column. + let show_both = preview_width >= MIN_PREVIEW_WIDTH; + let show_created = if show_both { + metrics.max_created_width > 0 + } else { + sort_key == ThreadSortKey::CreatedAt + }; + let show_updated = if show_both { + metrics.max_updated_width > 0 + } else { + sort_key == ThreadSortKey::UpdatedAt + }; + + ColumnVisibility { + show_created, + show_updated, + show_branch, + show_cwd, + } +} + #[cfg(test)] mod tests { use super::*; use chrono::Duration; + use codex_protocol::ThreadId; + use codex_protocol::models::ContentItem; + use codex_protocol::models::ResponseItem; + use codex_protocol::protocol::RolloutItem; + use codex_protocol::protocol::RolloutLine; + use codex_protocol::protocol::SessionMeta; + use codex_protocol::protocol::SessionMetaLine; + use codex_protocol::protocol::SessionSource; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; use insta::assert_snapshot; + use pretty_assertions::assert_eq; use serde_json::json; + use std::fs::FileTimes; + use std::fs::OpenOptions; + use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; @@ -1200,6 +1383,105 @@ mod tests { } } + fn set_rollout_mtime(path: &Path, updated_at: DateTime) { + let times = FileTimes::new().set_modified(updated_at.into()); + OpenOptions::new() + .append(true) + .open(path) + .expect("open rollout") + .set_times(times) + .expect("set times"); + } + + #[tokio::test] + async fn resume_picker_orders_by_updated_at() { + use uuid::Uuid; + + let tempdir = tempfile::tempdir().expect("tempdir"); + let sessions_root = tempdir.path().join("sessions"); + std::fs::create_dir_all(&sessions_root).expect("mkdir sessions root"); + + let now = Utc::now(); + + let write_rollout = |ts: DateTime, preview: &str| -> PathBuf { + let dir = sessions_root + .join(ts.format("%Y").to_string()) + .join(ts.format("%m").to_string()) + .join(ts.format("%d").to_string()); + std::fs::create_dir_all(&dir).expect("mkdir date dirs"); + let filename = format!( + "rollout-{}-{}.jsonl", + ts.format("%Y-%m-%dT%H-%M-%S"), + Uuid::new_v4() + ); + let path = dir.join(filename); + let meta = SessionMeta { + id: ThreadId::new(), + forked_from_id: None, + timestamp: ts.to_rfc3339(), + cwd: PathBuf::from("/tmp"), + originator: String::from("user"), + cli_version: String::from("0.0.0"), + source: SessionSource::Cli, + model_provider: Some(String::from("openai")), + base_instructions: None, + dynamic_tools: None, + }; + let meta_line = RolloutLine { + timestamp: ts.to_rfc3339(), + item: RolloutItem::SessionMeta(SessionMetaLine { meta, git: None }), + }; + let user_line = RolloutLine { + timestamp: ts.to_rfc3339(), + item: RolloutItem::ResponseItem(ResponseItem::Message { + id: None, + role: String::from("user"), + content: vec![ContentItem::InputText { + text: preview.to_string(), + }], + end_turn: None, + phase: None, + }), + }; + let meta_json = serde_json::to_string(&meta_line).expect("serialize meta"); + let user_json = serde_json::to_string(&user_line).expect("serialize user"); + std::fs::write(&path, format!("{meta_json}\n{user_json}\n")).expect("write rollout"); + path + }; + + let created_a = now - Duration::minutes(1); + let created_b = now - Duration::minutes(2); + + let path_a = write_rollout(created_a, "A (created newer)"); + let path_b = write_rollout(created_b, "B (created older)"); + + set_rollout_mtime(&path_a, now - Duration::minutes(10)); + set_rollout_mtime(&path_b, now - Duration::seconds(10)); + + let page = RolloutRecorder::list_threads( + tempdir.path(), + PAGE_SIZE, + None, + ThreadSortKey::UpdatedAt, + INTERACTIVE_SESSION_SOURCES, + Some(&[String::from("openai")]), + "openai", + ) + .await + .expect("list threads"); + + let rows = rows_from_items(page.items); + let previews: Vec = rows.iter().map(|row| row.preview.clone()).collect(); + + assert_eq!( + previews, + vec![ + "B (created older)".to_string(), + "A (created newer)".to_string() + ] + ); + } + #[test] fn head_to_row_uses_first_user_message() { let item = ThreadItem { @@ -1374,7 +1656,7 @@ mod tests { let area = frame.area(); let segments = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(area); - render_column_headers(&mut frame, segments[0], &metrics); + render_column_headers(&mut frame, segments[0], &metrics, state.sort_key); render_list(&mut frame, segments[1], &state, &metrics); } terminal.flush().expect("flush"); @@ -1517,13 +1799,19 @@ mod tests { .areas(area); frame.render_widget_ref( - Line::from(vec!["Resume a previous session".bold().cyan()]), + Line::from(vec![ + "Resume a previous session".bold().cyan(), + " ".into(), + "Sort:".dim(), + " ".into(), + "Created at".magenta(), + ]), header, ); frame.render_widget_ref(Line::from("Type to search".dim()), search); - render_column_headers(&mut frame, columns, &metrics); + render_column_headers(&mut frame, columns, &metrics, state.sort_key); render_list(&mut frame, list, &state, &metrics); let hint_line: Line = vec![ @@ -1535,6 +1823,9 @@ mod tests { " ".dim(), key_hint::ctrl(KeyCode::Char('c')).into(), " to quit ".dim(), + " ".dim(), + key_hint::plain(KeyCode::Tab).into(), + " to toggle sort ".dim(), ] .into(); frame.render_widget_ref(hint_line, hint); @@ -1634,7 +1925,7 @@ mod tests { let area = frame.area(); let segments = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(area); - render_column_headers(&mut frame, segments[0], &metrics); + render_column_headers(&mut frame, segments[0], &metrics, state.sort_key); render_list(&mut frame, segments[1], &state, &metrics); } terminal.flush().expect("flush"); @@ -1744,6 +2035,85 @@ mod tests { assert!(guard[0].search_token.is_none()); } + #[test] + fn column_visibility_hides_extra_date_column_when_narrow() { + let metrics = ColumnMetrics { + max_created_width: 8, + max_updated_width: 12, + max_branch_width: 0, + max_cwd_width: 0, + labels: Vec::new(), + }; + + let created = column_visibility(30, &metrics, ThreadSortKey::CreatedAt); + assert_eq!( + created, + ColumnVisibility { + show_created: true, + show_updated: false, + show_branch: false, + show_cwd: false, + } + ); + + let updated = column_visibility(30, &metrics, ThreadSortKey::UpdatedAt); + assert_eq!( + updated, + ColumnVisibility { + show_created: false, + show_updated: true, + show_branch: false, + show_cwd: false, + } + ); + + let wide = column_visibility(40, &metrics, ThreadSortKey::CreatedAt); + assert_eq!( + wide, + ColumnVisibility { + show_created: true, + show_updated: true, + show_branch: false, + show_cwd: false, + } + ); + } + + #[tokio::test] + async fn toggle_sort_key_reloads_with_new_sort() { + let recorded_requests: Arc>> = Arc::new(Mutex::new(Vec::new())); + let request_sink = recorded_requests.clone(); + let loader: PageLoader = Arc::new(move |req: PageLoadRequest| { + request_sink.lock().unwrap().push(req); + }); + + let mut state = PickerState::new( + PathBuf::from("/tmp"), + FrameRequester::test_dummy(), + loader, + String::from("openai"), + true, + None, + SessionPickerAction::Resume, + ); + + state.start_initial_load(); + { + let guard = recorded_requests.lock().unwrap(); + assert_eq!(guard.len(), 1); + assert_eq!(guard[0].sort_key, ThreadSortKey::CreatedAt); + } + + state + .handle_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)) + .await + .unwrap(); + + let guard = recorded_requests.lock().unwrap(); + assert_eq!(guard.len(), 2); + assert_eq!(guard[1].sort_key, ThreadSortKey::UpdatedAt); + } + #[tokio::test] async fn page_navigation_uses_view_rows() { let loader: PageLoader = Arc::new(|_| {}); diff --git a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_screen.snap b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_screen.snap index 79a169a06d..885ba88842 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_screen.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_screen.snap @@ -1,14 +1,13 @@ --- source: tui/src/resume_picker.rs -assertion_line: 1438 expression: snapshot --- -Resume a previous session +Resume a previous session Sort: Created at Type to search - Updated Branch CWD Conversation + Created at Updated at Branch CWD Conversation No sessions yet -enter to resume esc to start new ctrl + c to quit +enter to resume esc to start new ctrl + c to quit tab to toggle sort diff --git a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_table.snap b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_table.snap index c029043219..1505d6e7e3 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_table.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_table.snap @@ -2,7 +2,7 @@ source: tui/src/resume_picker.rs expression: snapshot --- - Updated Branch CWD Conversation - 42 seconds ago - - Fix resume picker timestamps -> 35 minutes ago - - Investigate lazy pagination cap - 2 hours ago - - Explain the codebase + Created at Updated at Branch CWD Conversation + 16 minutes ago 42 seconds ago - - Fix resume picker timestamps +> 1 hour ago 35 minutes ago - - Investigate lazy pagination cap + 2 hours ago 2 hours ago - - Explain the codebase diff --git a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap index e9bca47d40..d001fff0b9 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__resume_picker__tests__resume_picker_thread_names.snap @@ -1,8 +1,7 @@ --- source: tui/src/resume_picker.rs -assertion_line: 1683 expression: snapshot --- - Updated Branch CWD Conversation -> 2 days ago - - Keep this for now - 3 days ago - - Named thread + Created at Updated at Branch CWD Conversation +> - 2 days ago - - Keep this for now + - 3 days ago - - Named thread From 5c0fd62ff10bd6abff9ff41884b303c0290d7fa4 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Thu, 5 Feb 2026 10:03:10 -0800 Subject: [PATCH 69/90] fix(tui): fix resume_picker_orders_by_updated_at test (#10769) I think this was due to https://github.com/openai/codex/issues/10752 landing and not rebased on top of 9ee746afd6273306e5f6e9ed1f7f48ac4e212c23 --- codex-rs/tui/src/resume_picker.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/codex-rs/tui/src/resume_picker.rs b/codex-rs/tui/src/resume_picker.rs index d0fc1146b1..85fdbb160e 100644 --- a/codex-rs/tui/src/resume_picker.rs +++ b/codex-rs/tui/src/resume_picker.rs @@ -1327,13 +1327,13 @@ mod tests { use super::*; use chrono::Duration; use codex_protocol::ThreadId; - use codex_protocol::models::ContentItem; - use codex_protocol::models::ResponseItem; + use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; + use codex_protocol::protocol::UserMessageEvent; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; @@ -1433,15 +1433,12 @@ mod tests { }; let user_line = RolloutLine { timestamp: ts.to_rfc3339(), - item: RolloutItem::ResponseItem(ResponseItem::Message { - id: None, - role: String::from("user"), - content: vec![ContentItem::InputText { - text: preview.to_string(), - }], - end_turn: None, - phase: None, - }), + item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent { + message: preview.to_string(), + images: None, + text_elements: Vec::new(), + local_images: Vec::new(), + })), }; let meta_json = serde_json::to_string(&meta_line).expect("serialize meta"); let user_json = serde_json::to_string(&user_line).expect("serialize user"); From 3582b74d018c3b472fd28e1482efe277af80ab4a Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Thu, 5 Feb 2026 10:46:06 -0800 Subject: [PATCH 70/90] fix(auth): isolate chatgptAuthTokens concept to auth manager and app-server (#10423) So that the rest of the codebase (like TUI) don't need to be concerned whether ChatGPT auth was handled by Codex itself or passed in via app-server's external auth mode. --- codex-rs/Cargo.lock | 1 - .../app-server/src/codex_message_processor.rs | 15 ++++++---- codex-rs/cli/src/login.rs | 8 ++--- codex-rs/core/src/auth.rs | 30 ++++++++++++------- codex-rs/core/src/client.rs | 8 ++--- codex-rs/core/src/codex.rs | 10 ++++--- codex-rs/core/src/models_manager/manager.rs | 13 ++++---- codex-rs/core/tests/responses_headers.rs | 11 +++---- codex-rs/core/tests/suite/client.rs | 3 +- .../core/tests/suite/client_websockets.rs | 3 +- codex-rs/otel/Cargo.toml | 1 - codex-rs/otel/src/lib.rs | 7 +++++ codex-rs/otel/src/traces/otel_manager.rs | 4 +-- codex-rs/otel/tests/suite/manager_metrics.rs | 11 ++++--- codex-rs/otel/tests/suite/runtime_summary.rs | 4 +-- codex-rs/otel/tests/suite/snapshot.rs | 9 ++++-- codex-rs/tui/src/app.rs | 6 +++- codex-rs/tui/src/lib.rs | 4 +-- codex-rs/tui/src/onboarding/auth.rs | 7 ++--- codex-rs/tui/src/status/helpers.rs | 8 ++--- 20 files changed, 92 insertions(+), 71 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 4e1758355f..e90b25ee9e 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1917,7 +1917,6 @@ version = "0.0.0" dependencies = [ "chrono", "codex-api", - "codex-app-server-protocol", "codex-protocol", "codex-utils-absolute-path", "codex-utils-string", diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 6c0956acf5..ea60d20877 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -152,6 +152,7 @@ use codex_core::SessionMeta; use codex_core::ThreadConfigSnapshot; use codex_core::ThreadManager; use codex_core::ThreadSortKey as CoreThreadSortKey; +use codex_core::auth::AuthMode as CoreAuthMode; use codex_core::auth::CLIENT_ID; use codex_core::auth::login_with_api_key; use codex_core::auth::login_with_chatgpt_auth_tokens; @@ -1188,7 +1189,7 @@ impl CodexMessageProcessor { .await; let payload_v2 = AccountUpdatedNotification { - auth_mode: self.auth_manager.get_auth_mode(), + auth_mode: self.auth_manager.get_api_auth_mode(), }; self.outgoing .send_server_notification(ServerNotification::AccountUpdated(payload_v2)) @@ -1336,14 +1337,16 @@ impl CodexMessageProcessor { } let account = match self.auth_manager.auth_cached() { - Some(auth) => Some(match auth { - CodexAuth::ApiKey(_) => Account::ApiKey {}, - CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) => { + Some(auth) => match auth.auth_mode() { + CoreAuthMode::ApiKey => Some(Account::ApiKey {}), + CoreAuthMode::Chatgpt => { let email = auth.get_account_email(); let plan_type = auth.account_plan_type(); match (email, plan_type) { - (Some(email), Some(plan_type)) => Account::Chatgpt { email, plan_type }, + (Some(email), Some(plan_type)) => { + Some(Account::Chatgpt { email, plan_type }) + } _ => { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, @@ -1357,7 +1360,7 @@ impl CodexMessageProcessor { } } } - }), + }, None => None, }; diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index 01a830acb2..8cb6f3d01f 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -1,7 +1,7 @@ -use codex_app_server_protocol::AuthMode; use codex_common::CliConfigOverrides; use codex_core::CodexAuth; use codex_core::auth::AuthCredentialsStoreMode; +use codex_core::auth::AuthMode; use codex_core::auth::CLIENT_ID; use codex_core::auth::login_with_api_key; use codex_core::auth::logout; @@ -225,7 +225,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { let config = load_config_or_exit(cli_config_overrides).await; match CodexAuth::from_auth_storage(&config.codex_home, config.cli_auth_credentials_store_mode) { - Ok(Some(auth)) => match auth.api_auth_mode() { + Ok(Some(auth)) => match auth.auth_mode() { AuthMode::ApiKey => match auth.get_token() { Ok(api_key) => { eprintln!("Logged in using an API key - {}", safe_format_key(&api_key)); @@ -240,10 +240,6 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { eprintln!("Logged in using ChatGPT"); std::process::exit(0); } - AuthMode::ChatgptAuthTokens => { - eprintln!("Logged in using ChatGPT (external tokens)"); - std::process::exit(0); - } }, Ok(None) => { eprintln!("Not logged in"); diff --git a/codex-rs/core/src/auth.rs b/codex-rs/core/src/auth.rs index fb4114a7c2..7e58ff125f 100644 --- a/codex-rs/core/src/auth.rs +++ b/codex-rs/core/src/auth.rs @@ -16,6 +16,7 @@ use std::sync::Mutex; use std::sync::RwLock; use codex_app_server_protocol::AuthMode as ApiAuthMode; +use codex_otel::TelemetryAuthMode; use codex_protocol::config_types::ForcedLoginMethod; pub use crate::auth::storage::AuthCredentialsStoreMode; @@ -47,6 +48,15 @@ pub enum AuthMode { Chatgpt, } +impl From for TelemetryAuthMode { + fn from(mode: AuthMode) -> Self { + match mode { + AuthMode::ApiKey => TelemetryAuthMode::ApiKey, + AuthMode::Chatgpt => TelemetryAuthMode::Chatgpt, + } + } +} + /// Authentication mechanism used by the current user. #[derive(Debug, Clone)] pub enum CodexAuth { @@ -186,7 +196,7 @@ impl CodexAuth { load_auth(codex_home, false, auth_credentials_store_mode) } - pub fn internal_auth_mode(&self) -> AuthMode { + pub fn auth_mode(&self) -> AuthMode { match self { Self::ApiKey(_) => AuthMode::ApiKey, Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => AuthMode::Chatgpt, @@ -202,14 +212,14 @@ impl CodexAuth { } pub fn is_chatgpt_auth(&self) -> bool { - self.internal_auth_mode() == AuthMode::Chatgpt + self.auth_mode() == AuthMode::Chatgpt } pub fn is_external_chatgpt_tokens(&self) -> bool { matches!(self, Self::ChatgptAuthTokens(_)) } - /// Returns `None` is `is_internal_auth_mode() != AuthMode::ApiKey`. + /// Returns `None` if `auth_mode() != AuthMode::ApiKey`. pub fn api_key(&self) -> Option<&str> { match self { Self::ApiKey(auth) => Some(auth.api_key.as_str()), @@ -434,7 +444,7 @@ pub fn enforce_login_restrictions(config: &Config) -> std::io::Result<()> { }; if let Some(required_method) = config.forced_login_method { - let method_violation = match (required_method, auth.internal_auth_mode()) { + let method_violation = match (required_method, auth.auth_mode()) { (ForcedLoginMethod::Api, AuthMode::ApiKey) => None, (ForcedLoginMethod::Chatgpt, AuthMode::Chatgpt) => None, (ForcedLoginMethod::Api, AuthMode::Chatgpt) => Some( @@ -1158,14 +1168,12 @@ impl AuthManager { Ok(removed) } - pub fn get_auth_mode(&self) -> Option { + pub fn get_api_auth_mode(&self) -> Option { self.auth_cached().as_ref().map(CodexAuth::api_auth_mode) } - pub fn get_internal_auth_mode(&self) -> Option { - self.auth_cached() - .as_ref() - .map(CodexAuth::internal_auth_mode) + pub fn auth_mode(&self) -> Option { + self.auth_cached().as_ref().map(CodexAuth::auth_mode) } async fn refresh_if_stale(&self, auth: &CodexAuth) -> Result { @@ -1373,7 +1381,7 @@ mod tests { .unwrap() .unwrap(); assert_eq!(None, auth.api_key()); - assert_eq!(AuthMode::Chatgpt, auth.internal_auth_mode()); + assert_eq!(AuthMode::Chatgpt, auth.auth_mode()); let auth_dot_json = auth .get_current_auth_json() @@ -1418,7 +1426,7 @@ mod tests { let auth = super::load_auth(dir.path(), false, AuthCredentialsStoreMode::File) .unwrap() .unwrap(); - assert_eq!(auth.internal_auth_mode(), AuthMode::ApiKey); + assert_eq!(auth.auth_mode(), AuthMode::ApiKey); assert_eq!(auth.api_key(), Some("sk-test-key")); assert!(auth.get_token_data().is_err()); diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 570cdc830b..5acb3d6da6 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -225,7 +225,7 @@ impl ModelClient { let api_provider = self .state .provider - .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); let request_telemetry = Self::build_request_telemetry(otel_manager); @@ -271,7 +271,7 @@ impl ModelClient { let api_provider = self .state .provider - .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))?; let api_auth = auth_provider_from_auth(auth, &self.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); let request_telemetry = Self::build_request_telemetry(otel_manager); @@ -557,7 +557,7 @@ impl ModelClientSession { .client .state .provider - .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.client.state.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); let (request_telemetry, sse_telemetry) = Self::build_streaming_telemetry(otel_manager); @@ -622,7 +622,7 @@ impl ModelClientSession { .client .state .provider - .to_api_provider(auth.as_ref().map(CodexAuth::internal_auth_mode))?; + .to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))?; let api_auth = auth_provider_from_auth(auth.clone(), &self.client.state.provider)?; let compression = self.responses_request_compression(auth.as_ref()); diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 95c13ec9df..77d6d6a73c 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -207,6 +207,7 @@ use crate::util::backoff; use crate::windows_sandbox::WindowsSandboxLevelExt; use codex_async_utils::OrCancelExt; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig; @@ -949,13 +950,14 @@ impl Session { maybe_push_unstable_features_warning(&config, &mut post_session_configured_events); let auth = auth.as_ref(); + let auth_mode = auth.map(CodexAuth::auth_mode).map(TelemetryAuthMode::from); let otel_manager = OtelManager::new( conversation_id, session_configuration.collaboration_mode.model(), session_configuration.collaboration_mode.model(), auth.and_then(CodexAuth::get_account_id), auth.and_then(CodexAuth::get_account_email), - auth.map(CodexAuth::api_auth_mode), + auth_mode, config.otel.log_user_prompt, terminal::user_agent(), session_configuration.session_source.clone(), @@ -4518,7 +4520,7 @@ async fn try_run_sampling_request( approval_policy = turn_context.approval_policy, sandbox_policy = turn_context.sandbox_policy, effort = turn_context.reasoning_effort, - auth_mode = sess.services.auth_manager.get_auth_mode(), + auth_mode = sess.services.auth_manager.auth_mode(), features = sess.features.enabled_features(), ); @@ -4842,7 +4844,7 @@ mod tests { use crate::tools::registry::ToolHandler; use crate::turn_diff_tracker::TurnDiffTracker; use codex_app_server_protocol::AppInfo; - use codex_app_server_protocol::AuthMode; + use codex_otel::TelemetryAuthMode; use codex_protocol::models::BaseInstructions; use codex_protocol::models::ContentItem; use codex_protocol::models::ResponseInputItem; @@ -5652,7 +5654,7 @@ mod tests { model_info.slug.as_str(), None, Some("test@test.com".to_string()), - Some(AuthMode::Chatgpt), + Some(TelemetryAuthMode::Chatgpt), false, "test".to_string(), session_source, diff --git a/codex-rs/core/src/models_manager/manager.rs b/codex-rs/core/src/models_manager/manager.rs index fffc280736..2d85185076 100644 --- a/codex-rs/core/src/models_manager/manager.rs +++ b/codex-rs/core/src/models_manager/manager.rs @@ -61,7 +61,7 @@ impl ModelsManager { let cache_path = codex_home.join(MODEL_CACHE_FILE); let cache_manager = ModelsCacheManager::new(cache_path, DEFAULT_MODEL_CACHE_TTL); Self { - local_models: builtin_model_presets(auth_manager.get_internal_auth_mode()), + local_models: builtin_model_presets(auth_manager.auth_mode()), remote_models: RwLock::new(Self::load_remote_models_from_file().unwrap_or_default()), auth_manager, etag: RwLock::new(None), @@ -175,7 +175,7 @@ impl ModelsManager { refresh_strategy: RefreshStrategy, ) -> CoreResult<()> { if !config.features.enabled(Feature::RemoteModels) - || self.auth_manager.get_internal_auth_mode() == Some(AuthMode::ApiKey) + || self.auth_manager.auth_mode() == Some(AuthMode::ApiKey) { return Ok(()); } @@ -204,7 +204,7 @@ impl ModelsManager { let _timer = codex_otel::start_global_timer("codex.remote_models.fetch_update.duration_ms", &[]); let auth = self.auth_manager.auth().await; - let auth_mode = self.auth_manager.get_internal_auth_mode(); + let auth_mode = self.auth_manager.auth_mode(); let api_provider = self.provider.to_api_provider(auth_mode)?; let api_auth = auth_provider_from_auth(auth.clone(), &self.provider)?; let transport = ReqwestTransport::new(build_reqwest_client()); @@ -275,10 +275,7 @@ impl ModelsManager { let remote_presets: Vec = remote_models.into_iter().map(Into::into).collect(); let existing_presets = self.local_models.clone(); let mut merged_presets = ModelPreset::merge(remote_presets, existing_presets); - let chatgpt_mode = matches!( - self.auth_manager.get_internal_auth_mode(), - Some(AuthMode::Chatgpt) - ); + let chatgpt_mode = matches!(self.auth_manager.auth_mode(), Some(AuthMode::Chatgpt)); merged_presets = ModelPreset::filter_by_auth(merged_presets, chatgpt_mode); for preset in &mut merged_presets { @@ -322,7 +319,7 @@ impl ModelsManager { let cache_path = codex_home.join(MODEL_CACHE_FILE); let cache_manager = ModelsCacheManager::new(cache_path, DEFAULT_MODEL_CACHE_TTL); Self { - local_models: builtin_model_presets(auth_manager.get_internal_auth_mode()), + local_models: builtin_model_presets(auth_manager.auth_mode()), remote_models: RwLock::new(Self::load_remote_models_from_file().unwrap_or_default()), auth_manager, etag: RwLock::new(None), diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 9e70264bf9..04cfdc037b 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -1,7 +1,6 @@ use std::process::Command; use std::sync::Arc; -use codex_app_server_protocol::AuthMode; use codex_core::AuthManager; use codex_core::CodexAuth; use codex_core::ContentItem; @@ -14,6 +13,7 @@ use codex_core::WEB_SEARCH_ELIGIBLE_HEADER; use codex_core::WireApi; use codex_core::models_manager::manager::ModelsManager; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::WebSearchMode; @@ -72,7 +72,7 @@ async fn responses_stream_includes_subagent_header_on_review() { let config = Arc::new(config); let conversation_id = ThreadId::new(); - let auth_mode = AuthMode::Chatgpt; + let auth_mode = TelemetryAuthMode::Chatgpt; let session_source = SessionSource::SubAgent(SubAgentSource::Review); let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); let otel_manager = OtelManager::new( @@ -182,7 +182,7 @@ async fn responses_stream_includes_subagent_header_on_other() { let config = Arc::new(config); let conversation_id = ThreadId::new(); - let auth_mode = AuthMode::Chatgpt; + let auth_mode = TelemetryAuthMode::Chatgpt; let session_source = SessionSource::SubAgent(SubAgentSource::Other("my-task".to_string())); let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); @@ -350,8 +350,9 @@ async fn responses_respects_model_info_overrides_from_config() { let config = Arc::new(config); let conversation_id = ThreadId::new(); - let auth_mode = - AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")).get_auth_mode(); + let auth_mode = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("Test API Key")) + .auth_mode() + .map(TelemetryAuthMode::from); let session_source = SessionSource::SubAgent(SubAgentSource::Other("override-check".to_string())); let model_info = ModelsManager::construct_model_info_offline(model.as_str(), &config); diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 731ce97c0d..7debde217f 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -21,6 +21,7 @@ use codex_core::protocol::EventMsg; use codex_core::protocol::Op; use codex_core::protocol::SessionSource; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_protocol::ThreadId; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; @@ -1257,7 +1258,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { model_info.slug.as_str(), None, Some("test@test.com".to_string()), - auth_manager.get_auth_mode(), + auth_manager.auth_mode().map(TelemetryAuthMode::from), false, "test".to_string(), SessionSource::Exec, diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index e3a1ef0a8e..f78b7b1a3d 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -14,6 +14,7 @@ use codex_core::features::Feature; use codex_core::models_manager::manager::ModelsManager; use codex_core::protocol::SessionSource; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_otel::metrics::MetricsClient; use codex_otel::metrics::MetricsConfig; use codex_protocol::ThreadId; @@ -446,7 +447,7 @@ async fn websocket_harness_with_runtime_metrics( model_info.slug.as_str(), None, Some("test@test.com".to_string()), - auth_manager.get_auth_mode(), + auth_manager.auth_mode().map(TelemetryAuthMode::from), false, "test".to_string(), SessionSource::Exec, diff --git a/codex-rs/otel/Cargo.toml b/codex-rs/otel/Cargo.toml index c0bcb365b6..6e6321d2e5 100644 --- a/codex-rs/otel/Cargo.toml +++ b/codex-rs/otel/Cargo.toml @@ -21,7 +21,6 @@ disable-default-metrics-exporter = [] [dependencies] chrono = { workspace = true } -codex-app-server-protocol = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-string = { workspace = true } codex-api = { workspace = true } diff --git a/codex-rs/otel/src/lib.rs b/codex-rs/otel/src/lib.rs index ed6e5a1c9d..a86e5ef525 100644 --- a/codex-rs/otel/src/lib.rs +++ b/codex-rs/otel/src/lib.rs @@ -31,6 +31,13 @@ pub enum ToolDecisionSource { User, } +/// Maps to core AuthMode to avoid a circular dependency on codex-core. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] +pub enum TelemetryAuthMode { + ApiKey, + Chatgpt, +} + #[derive(Debug, Clone)] pub struct OtelEventMetadata { pub(crate) conversation_id: ThreadId, diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index bde9515c20..b174dabe68 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -1,3 +1,4 @@ +use crate::TelemetryAuthMode; use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; @@ -15,7 +16,6 @@ use chrono::SecondsFormat; use chrono::Utc; use codex_api::ApiError; use codex_api::ResponseEvent; -use codex_app_server_protocol::AuthMode; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::models::ResponseItem; @@ -57,7 +57,7 @@ impl OtelManager { slug: &str, account_id: Option, account_email: Option, - auth_mode: Option, + auth_mode: Option, log_user_prompts: bool, terminal_type: String, session_source: SessionSource, diff --git a/codex-rs/otel/tests/suite/manager_metrics.rs b/codex-rs/otel/tests/suite/manager_metrics.rs index ad98543bc1..164bb43b3a 100644 --- a/codex-rs/otel/tests/suite/manager_metrics.rs +++ b/codex-rs/otel/tests/suite/manager_metrics.rs @@ -2,8 +2,8 @@ use crate::harness::attributes_to_map; use crate::harness::build_metrics_with_defaults; use crate::harness::find_metric; use crate::harness::latest_metrics; -use codex_app_server_protocol::AuthMode; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_otel::metrics::Result; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; @@ -22,7 +22,7 @@ fn manager_attaches_metadata_tags_to_metrics() -> Result<()> { "gpt-5.1", Some("account-id".to_string()), None, - Some(AuthMode::ApiKey), + Some(TelemetryAuthMode::ApiKey), true, "tty".to_string(), SessionSource::Cli, @@ -52,7 +52,10 @@ fn manager_attaches_metadata_tags_to_metrics() -> Result<()> { "app.version".to_string(), env!("CARGO_PKG_VERSION").to_string(), ), - ("auth_mode".to_string(), AuthMode::ApiKey.to_string()), + ( + "auth_mode".to_string(), + TelemetryAuthMode::ApiKey.to_string(), + ), ("model".to_string(), "gpt-5.1".to_string()), ("service".to_string(), "codex-cli".to_string()), ("session_source".to_string(), "cli".to_string()), @@ -73,7 +76,7 @@ fn manager_allows_disabling_metadata_tags() -> Result<()> { "gpt-4o", Some("account-id".to_string()), None, - Some(AuthMode::ApiKey), + Some(TelemetryAuthMode::ApiKey), true, "tty".to_string(), SessionSource::Cli, diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs index 811c801fce..71d9a140cf 100644 --- a/codex-rs/otel/tests/suite/runtime_summary.rs +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -1,7 +1,7 @@ -use codex_app_server_protocol::AuthMode; use codex_otel::OtelManager; use codex_otel::RuntimeMetricTotals; use codex_otel::RuntimeMetricsSummary; +use codex_otel::TelemetryAuthMode; use codex_otel::metrics::MetricsClient; use codex_otel::metrics::MetricsConfig; use codex_otel::metrics::Result; @@ -26,7 +26,7 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( "gpt-5.1", Some("account-id".to_string()), None, - Some(AuthMode::ApiKey), + Some(TelemetryAuthMode::ApiKey), true, "tty".to_string(), SessionSource::Cli, diff --git a/codex-rs/otel/tests/suite/snapshot.rs b/codex-rs/otel/tests/suite/snapshot.rs index f0a7a18c54..aa7990d062 100644 --- a/codex-rs/otel/tests/suite/snapshot.rs +++ b/codex-rs/otel/tests/suite/snapshot.rs @@ -1,7 +1,7 @@ use crate::harness::attributes_to_map; use crate::harness::find_metric; -use codex_app_server_protocol::AuthMode; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_otel::metrics::MetricsClient; use codex_otel::metrics::MetricsConfig; use codex_otel::metrics::Result; @@ -75,7 +75,7 @@ fn manager_snapshot_metrics_collects_without_shutdown() -> Result<()> { "gpt-5.1", Some("account-id".to_string()), None, - Some(AuthMode::ApiKey), + Some(TelemetryAuthMode::ApiKey), true, "tty".to_string(), SessionSource::Cli, @@ -107,7 +107,10 @@ fn manager_snapshot_metrics_collects_without_shutdown() -> Result<()> { "app.version".to_string(), env!("CARGO_PKG_VERSION").to_string(), ), - ("auth_mode".to_string(), AuthMode::ApiKey.to_string()), + ( + "auth_mode".to_string(), + TelemetryAuthMode::ApiKey.to_string(), + ), ("model".to_string(), "gpt-5.1".to_string()), ("service".to_string(), "codex-cli".to_string()), ("session_source".to_string(), "cli".to_string()), diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 8c3bd5188c..8ef4283c6b 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -60,6 +60,7 @@ use codex_core::protocol::TokenUsage; #[cfg(target_os = "windows")] use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_otel::OtelManager; +use codex_otel::TelemetryAuthMode; use codex_protocol::ThreadId; use codex_protocol::config_types::Personality; #[cfg(target_os = "windows")] @@ -975,13 +976,16 @@ impl App { } else { FeedbackAudience::External }; + let auth_mode = auth_ref + .map(CodexAuth::auth_mode) + .map(TelemetryAuthMode::from); let otel_manager = OtelManager::new( ThreadId::new(), model.as_str(), model.as_str(), auth_ref.and_then(CodexAuth::get_account_id), auth_ref.and_then(CodexAuth::get_account_email), - auth_ref.map(CodexAuth::api_auth_mode), + auth_mode, config.otel.log_user_prompt, codex_core::terminal::user_agent(), SessionSource::Cli, diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 95e26fe675..daabb60273 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -7,7 +7,6 @@ use additional_dirs::add_dir_warning_message; use app::App; pub use app::AppExitInfo; pub use app::ExitReason; -use codex_app_server_protocol::AuthMode; use codex_cloud_requirements::cloud_requirements_loader; use codex_common::oss::ensure_oss_provider_ready; use codex_common::oss::get_default_model_for_oss_provider; @@ -16,6 +15,7 @@ use codex_core::CodexAuth; use codex_core::INTERACTIVE_SESSION_SOURCES; use codex_core::RolloutRecorder; use codex_core::ThreadSortKey; +use codex_core::auth::AuthMode; use codex_core::auth::enforce_login_restrictions; use codex_core::config::Config; use codex_core::config::ConfigBuilder; @@ -841,7 +841,7 @@ fn get_login_status(config: &Config) -> LoginStatus { // to refresh the token. Block on it. let codex_home = config.codex_home.clone(); match CodexAuth::from_auth_storage(&codex_home, config.cli_auth_credentials_store_mode) { - Ok(Some(auth)) => LoginStatus::AuthMode(auth.api_auth_mode()), + Ok(Some(auth)) => LoginStatus::AuthMode(auth.auth_mode()), Ok(None) => LoginStatus::NotAuthenticated, Err(err) => { error!("Failed to read auth.json: {err}"); diff --git a/codex-rs/tui/src/onboarding/auth.rs b/codex-rs/tui/src/onboarding/auth.rs index 7378b6628e..c639d24a08 100644 --- a/codex-rs/tui/src/onboarding/auth.rs +++ b/codex-rs/tui/src/onboarding/auth.rs @@ -30,7 +30,7 @@ use ratatui::widgets::Paragraph; use ratatui::widgets::WidgetRef; use ratatui::widgets::Wrap; -use codex_app_server_protocol::AuthMode; +use codex_core::auth::AuthMode; use codex_protocol::config_types::ForcedLoginMethod; use std::sync::RwLock; @@ -661,10 +661,7 @@ impl AuthModeWidget { } fn handle_existing_chatgpt_login(&mut self) -> bool { - if matches!( - self.login_status, - LoginStatus::AuthMode(AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens) - ) { + if matches!(self.login_status, LoginStatus::AuthMode(AuthMode::Chatgpt)) { *self.sign_in_state.write().unwrap() = SignInState::ChatGptSuccess; self.request_frame.schedule_frame(); true diff --git a/codex-rs/tui/src/status/helpers.rs b/codex-rs/tui/src/status/helpers.rs index bc4cbb9c16..c819405412 100644 --- a/codex-rs/tui/src/status/helpers.rs +++ b/codex-rs/tui/src/status/helpers.rs @@ -3,7 +3,7 @@ use crate::text_formatting; use chrono::DateTime; use chrono::Local; use codex_core::AuthManager; -use codex_core::CodexAuth; +use codex_core::auth::AuthMode as CoreAuthMode; use codex_core::config::Config; use codex_core::project_doc::discover_project_doc_paths; use codex_protocol::account::PlanType; @@ -90,15 +90,15 @@ pub(crate) fn compose_account_display( ) -> Option { let auth = auth_manager.auth_cached()?; - match auth { - CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) => { + match auth.auth_mode() { + CoreAuthMode::ApiKey => Some(StatusAccountDisplay::ApiKey), + CoreAuthMode::Chatgpt => { let email = auth.get_account_email(); let plan = plan .map(|plan_type| title_case(format!("{plan_type:?}").as_str())) .or_else(|| Some("Unknown".to_string())); Some(StatusAccountDisplay::ChatGpt { email, plan }) } - CodexAuth::ApiKey(_) => Some(StatusAccountDisplay::ApiKey), } } From ddfb8bfd778bfe673cbb7f0aade8ba6d7ad78381 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 19:17:04 +0000 Subject: [PATCH 71/90] nit: gpt-5.3-codex announcement (#10775) --- announcement_tip.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/announcement_tip.toml b/announcement_tip.toml index b2cda2f044..930f72fb46 100644 --- a/announcement_tip.toml +++ b/announcement_tip.toml @@ -15,3 +15,8 @@ target_app = "cli" content = "This is a test announcement" version_regex = "^0\\.0\\.0$" to_date = "2026-05-10" + +[[announcements]] +content = "**BREAKING NEWS**: `gpt-5.3-codex` is out! Upgrade to `0.98.0` to try it." +from_date = "2025-02-01" +to_date = "2025-02-15" From 4df9f2020b75f57d9c345d5ed176f0fbcf6271e4 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 19:22:24 +0000 Subject: [PATCH 72/90] nit: gpt-5.3-codex announcement 2 (#10782) --- announcement_tip.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/announcement_tip.toml b/announcement_tip.toml index 930f72fb46..b0fdfd2c4c 100644 --- a/announcement_tip.toml +++ b/announcement_tip.toml @@ -18,5 +18,5 @@ to_date = "2026-05-10" [[announcements]] content = "**BREAKING NEWS**: `gpt-5.3-codex` is out! Upgrade to `0.98.0` to try it." -from_date = "2025-02-01" -to_date = "2025-02-15" +from_date = "2026-02-01" +to_date = "2026-02-15" From 901d5b8fd60afb6180cbf0d61ceb76cd0df0a078 Mon Sep 17 00:00:00 2001 From: iceweasel-oai Date: Thu, 5 Feb 2026 11:42:12 -0800 Subject: [PATCH 73/90] add sandbox policy and sandbox name to codex.tool.call metrics (#10711) This will give visibility into the comparative success rate of the Windows sandbox implementations compared to other platforms. --- codex-rs/core/src/exec.rs | 11 +++++ codex-rs/core/src/tools/registry.rs | 52 ++++++++++++++++++-- codex-rs/otel/src/traces/otel_manager.rs | 26 +++++----- codex-rs/otel/tests/suite/runtime_summary.rs | 3 +- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 3ac8d1956f..d4d508ced6 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -128,6 +128,17 @@ pub enum SandboxType { WindowsRestrictedToken, } +impl SandboxType { + pub(crate) fn as_metric_tag(self) -> &'static str { + match self { + SandboxType::None => "none", + SandboxType::MacosSeatbelt => "seatbelt", + SandboxType::LinuxSeccomp => "seccomp", + SandboxType::WindowsRestrictedToken => "windows_sandbox", + } + } +} + #[derive(Clone)] pub struct StdoutStream { pub sub_id: String, diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index f67db63943..3de041c573 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -3,11 +3,15 @@ use std::sync::Arc; use std::time::Duration; use crate::client_common::tools::ToolSpec; +use crate::exec::SandboxType; use crate::function_tool::FunctionCallError; +use crate::protocol::SandboxPolicy; +use crate::safety::get_platform_sandbox; use crate::tools::context::ToolInvocation; use crate::tools::context::ToolOutput; use crate::tools::context::ToolPayload; use async_trait::async_trait; +use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ResponseInputItem; use codex_utils_readiness::Readiness; use tracing::warn; @@ -73,19 +77,33 @@ impl ToolRegistry { let otel = invocation.turn.otel_manager.clone(); let payload_for_response = invocation.payload.clone(); let log_payload = payload_for_response.log_payload(); + let metric_tags = [ + ( + "sandbox", + sandbox_tag( + &invocation.turn.sandbox_policy, + invocation.turn.windows_sandbox_level, + ), + ), + ( + "sandbox_policy", + sandbox_policy_tag(&invocation.turn.sandbox_policy), + ), + ]; let handler = match self.handler(tool_name.as_ref()) { Some(handler) => handler, None => { let message = unsupported_tool_call_message(&invocation.payload, tool_name.as_ref()); - otel.tool_result( + otel.tool_result_with_tags( tool_name.as_ref(), &call_id_owned, log_payload.as_ref(), Duration::ZERO, false, &message, + &metric_tags, ); return Err(FunctionCallError::RespondToModel(message)); } @@ -93,13 +111,14 @@ impl ToolRegistry { if !handler.matches_kind(&invocation.payload) { let message = format!("tool {tool_name} invoked with incompatible payload"); - otel.tool_result( + otel.tool_result_with_tags( tool_name.as_ref(), &call_id_owned, log_payload.as_ref(), Duration::ZERO, false, &message, + &metric_tags, ); return Err(FunctionCallError::Fatal(message)); } @@ -107,10 +126,11 @@ impl ToolRegistry { let output_cell = tokio::sync::Mutex::new(None); let result = otel - .log_tool_result( + .log_tool_result_with_tags( tool_name.as_ref(), &call_id_owned, log_payload.as_ref(), + &metric_tags, || { let handler = handler.clone(); let output_cell = &output_cell; @@ -231,3 +251,29 @@ fn unsupported_tool_call_message(payload: &ToolPayload, tool_name: &str) -> Stri _ => format!("unsupported call: {tool_name}"), } } + +fn sandbox_tag(policy: &SandboxPolicy, windows_sandbox_level: WindowsSandboxLevel) -> &'static str { + if matches!(policy, SandboxPolicy::DangerFullAccess) { + return "none"; + } + if matches!(policy, SandboxPolicy::ExternalSandbox { .. }) { + return "external"; + } + if cfg!(target_os = "windows") && matches!(windows_sandbox_level, WindowsSandboxLevel::Elevated) + { + return "windows_elevated"; + } + + get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled) + .map(SandboxType::as_metric_tag) + .unwrap_or("none") +} + +fn sandbox_policy_tag(policy: &SandboxPolicy) -> &'static str { + match policy { + SandboxPolicy::ReadOnly => "read-only", + SandboxPolicy::WorkspaceWrite { .. } => "workspace-write", + SandboxPolicy::DangerFullAccess => "danger-full-access", + SandboxPolicy::ExternalSandbox { .. } => "external-sandbox", + } +} diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index b174dabe68..aa3006da01 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -566,11 +566,12 @@ impl OtelManager { ); } - pub async fn log_tool_result( + pub async fn log_tool_result_with_tags( &self, tool_name: &str, call_id: &str, arguments: &str, + extra_tags: &[(&str, &str)], f: F, ) -> Result<(String, bool), E> where @@ -587,13 +588,14 @@ impl OtelManager { Err(error) => (Cow::Owned(error.to_string()), false), }; - self.tool_result( + self.tool_result_with_tags( tool_name, call_id, arguments, duration, success, output.as_ref(), + extra_tags, ); result @@ -619,7 +621,8 @@ impl OtelManager { ); } - pub fn tool_result( + #[allow(clippy::too_many_arguments)] + pub fn tool_result_with_tags( &self, tool_name: &str, call_id: &str, @@ -627,18 +630,15 @@ impl OtelManager { duration: Duration, success: bool, output: &str, + extra_tags: &[(&str, &str)], ) { let success_str = if success { "true" } else { "false" }; - self.counter( - TOOL_CALL_COUNT_METRIC, - 1, - &[("tool", tool_name), ("success", success_str)], - ); - self.record_duration( - TOOL_CALL_DURATION_METRIC, - duration, - &[("tool", tool_name), ("success", success_str)], - ); + let mut tags = Vec::with_capacity(2 + extra_tags.len()); + tags.push(("tool", tool_name)); + tags.push(("success", success_str)); + tags.extend_from_slice(extra_tags); + self.counter(TOOL_CALL_COUNT_METRIC, 1, &tags); + self.record_duration(TOOL_CALL_DURATION_METRIC, duration, &tags); tracing::event!( tracing::Level::INFO, event.name = "codex.tool_result", diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs index 71d9a140cf..6ef2927f43 100644 --- a/codex-rs/otel/tests/suite/runtime_summary.rs +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -35,13 +35,14 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( manager.reset_runtime_metrics(); - manager.tool_result( + manager.tool_result_with_tags( "shell", "call-1", "{\"cmd\":\"echo\"}", Duration::from_millis(250), true, "ok", + &[], ); manager.record_api_request(1, Some(200), None, Duration::from_millis(300)); manager.record_websocket_request(Duration::from_millis(400), None); From 5fdf6f5efa2db8a06ed4c746c88a9f771c469f3d Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Thu, 5 Feb 2026 11:48:34 -0800 Subject: [PATCH 74/90] chore: rm web-search-eligible header (#10660) default-enablement of web_search is now client-side, no need to send eligibility headers to backend. Tested locally, headers no longer sent. will wait for corresponding backend change to deploy before merging --- codex-rs/core/src/client.rs | 33 ++----- codex-rs/core/src/codex.rs | 5 - codex-rs/core/src/compact.rs | 6 -- codex-rs/core/src/lib.rs | 1 - codex-rs/core/tests/responses_headers.rs | 95 +------------------ codex-rs/core/tests/suite/client.rs | 10 +- .../core/tests/suite/client_websockets.rs | 6 -- 7 files changed, 12 insertions(+), 144 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 5acb3d6da6..3631f3afb8 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -4,9 +4,9 @@ //! configuration and state needed to talk to a provider (auth, provider selection, conversation id, //! and feature-gated request behavior). //! -//! Per-turn settings (model selection, reasoning controls, telemetry context, web search -//! eligibility, and turn metadata) are passed explicitly to streaming and unary methods so that the -//! turn lifetime is visible at the call site. +//! Per-turn settings (model selection, reasoning controls, telemetry context, and turn metadata) +//! are passed explicitly to streaming and unary methods so that the turn lifetime is visible at the +//! call site. //! //! A [`ModelClientSession`] is created per turn and is used to stream one or more Responses API //! requests during that turn. It caches a Responses WebSocket connection (opened lazily) and @@ -82,7 +82,6 @@ use crate::model_provider_info::ModelProviderInfo; use crate::model_provider_info::WireApi; use crate::tools::spec::create_tools_json_for_responses_api; -pub const WEB_SEARCH_ELIGIBLE_HEADER: &str = "x-oai-web-search-eligible"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = @@ -115,9 +114,8 @@ struct ModelClientState { /// WebSocket fallback is session-scoped: once a turn activates the HTTP fallback, subsequent turns /// will also use HTTP for the remainder of the session. /// -/// Turn-scoped settings (model selection, reasoning controls, telemetry context, web search -/// eligibility, and turn metadata) are passed explicitly to the relevant methods to keep turn -/// lifetime visible at the call site. +/// Turn-scoped settings (model selection, reasoning controls, telemetry context, and turn metadata) +/// are passed explicitly to the relevant methods to keep turn lifetime visible at the call site. /// /// This type is cheap to clone. #[derive(Debug, Clone)] @@ -349,7 +347,6 @@ impl ModelClientSession { model_info: &ModelInfo, effort: Option, summary: ReasoningSummaryConfig, - web_search_eligible: bool, turn_metadata_header: Option<&str>, compression: Compression, ) -> ApiResponsesOptions { @@ -404,7 +401,6 @@ impl ModelClientSession { session_source: Some(self.client.state.session_source.clone()), extra_headers: build_responses_headers( self.client.state.beta_features_header.as_deref(), - web_search_eligible, Some(&self.turn_state), turn_metadata_header.as_ref(), ), @@ -529,7 +525,6 @@ impl ModelClientSession { otel_manager: &OtelManager, effort: Option, summary: ReasoningSummaryConfig, - web_search_eligible: bool, turn_metadata_header: Option<&str>, ) -> Result { if let Some(path) = &*CODEX_RS_SSE_FIXTURE { @@ -571,7 +566,6 @@ impl ModelClientSession { model_info, effort, summary, - web_search_eligible, turn_metadata_header, compression, ); @@ -604,7 +598,6 @@ impl ModelClientSession { otel_manager: &OtelManager, effort: Option, summary: ReasoningSummaryConfig, - web_search_eligible: bool, turn_metadata_header: Option<&str>, ) -> Result { let auth_manager = self.client.state.auth_manager.clone(); @@ -631,7 +624,6 @@ impl ModelClientSession { model_info, effort, summary, - web_search_eligible, turn_metadata_header, compression, ); @@ -687,9 +679,9 @@ impl ModelClientSession { /// Streams a single model request within the current turn. /// /// The caller is responsible for passing per-turn settings explicitly (model selection, - /// reasoning settings, telemetry context, web search eligibility, and turn metadata). This - /// method will prefer the Responses WebSocket transport when enabled and healthy, and will - /// fall back to the HTTP Responses API transport otherwise. + /// reasoning settings, telemetry context, and turn metadata). This method will prefer the + /// Responses WebSocket transport when enabled and healthy, and will fall back to the HTTP + /// Responses API transport otherwise. pub async fn stream( &mut self, prompt: &Prompt, @@ -697,7 +689,6 @@ impl ModelClientSession { otel_manager: &OtelManager, effort: Option, summary: ReasoningSummaryConfig, - web_search_eligible: bool, turn_metadata_header: Option<&str>, ) -> Result { let wire_api = self.client.state.provider.wire_api; @@ -713,7 +704,6 @@ impl ModelClientSession { otel_manager, effort, summary, - web_search_eligible, turn_metadata_header, ) .await @@ -724,7 +714,6 @@ impl ModelClientSession { otel_manager, effort, summary, - web_search_eligible, turn_metadata_header, ) .await @@ -772,12 +761,10 @@ fn build_api_prompt(prompt: &Prompt, instructions: String, tools_json: Vec, - web_search_eligible: bool, turn_state: Option<&Arc>>, turn_metadata_header: Option<&HeaderValue>, ) -> ApiHeaderMap { @@ -788,10 +775,6 @@ fn build_responses_headers( { headers.insert("x-codex-beta-features", header_value); } - headers.insert( - WEB_SEARCH_ELIGIBLE_HEADER, - HeaderValue::from_static(if web_search_eligible { "true" } else { "false" }), - ); if let Some(turn_state) = turn_state && let Some(state) = turn_state.get() && let Ok(header_value) = HeaderValue::from_str(state) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 77d6d6a73c..b4c2118834 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -4525,10 +4525,6 @@ async fn try_run_sampling_request( ); sess.persist_rollout_items(&[rollout_item]).await; - let web_search_eligible = !matches!( - turn_context.config.web_search_mode, - Some(WebSearchMode::Disabled) - ); let mut stream = client_session .stream( prompt, @@ -4536,7 +4532,6 @@ async fn try_run_sampling_request( &turn_context.otel_manager, turn_context.reasoning_effort, turn_context.reasoning_summary, - web_search_eligible, turn_metadata_header, ) .instrument(trace_span!("stream_request")) diff --git a/codex-rs/core/src/compact.rs b/codex-rs/core/src/compact.rs index 91b95759c0..ab9a9f15d2 100644 --- a/codex-rs/core/src/compact.rs +++ b/codex-rs/core/src/compact.rs @@ -20,7 +20,6 @@ use crate::truncate::TruncationPolicy; use crate::truncate::approx_token_count; use crate::truncate::truncate_text; use crate::util::backoff; -use codex_protocol::config_types::WebSearchMode; use codex_protocol::items::ContextCompactionItem; use codex_protocol::items::TurnItem; use codex_protocol::models::ContentItem; @@ -352,10 +351,6 @@ async fn drain_to_completed( turn_metadata_header: Option<&str>, prompt: &Prompt, ) -> CodexResult<()> { - let web_search_eligible = !matches!( - turn_context.config.web_search_mode, - Some(WebSearchMode::Disabled) - ); let mut stream = client_session .stream( prompt, @@ -363,7 +358,6 @@ async fn drain_to_completed( &turn_context.otel_manager, turn_context.reasoning_effort, turn_context.reasoning_summary, - web_search_eligible, turn_metadata_header, ) .await?; diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index ed7fc6e3e3..ff54bdc80c 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -130,7 +130,6 @@ mod user_shell_command; pub mod util; pub use apply_patch::CODEX_APPLY_PATCH_ARG1; -pub use client::WEB_SEARCH_ELIGIBLE_HEADER; pub use client::X_CODEX_TURN_METADATA_HEADER; pub use command_safety::is_dangerous_command; pub use command_safety::is_safe_command; diff --git a/codex-rs/core/tests/responses_headers.rs b/codex-rs/core/tests/responses_headers.rs index 04cfdc037b..83f1d10423 100644 --- a/codex-rs/core/tests/responses_headers.rs +++ b/codex-rs/core/tests/responses_headers.rs @@ -9,14 +9,12 @@ use codex_core::ModelProviderInfo; use codex_core::Prompt; use codex_core::ResponseEvent; use codex_core::ResponseItem; -use codex_core::WEB_SEARCH_ELIGIBLE_HEADER; use codex_core::WireApi; use codex_core::models_manager::manager::ModelsManager; use codex_otel::OtelManager; use codex_otel::TelemetryAuthMode; use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary; -use codex_protocol::config_types::WebSearchMode; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SubAgentSource; use core_test_support::load_default_config_for_test; @@ -87,7 +85,6 @@ async fn responses_stream_includes_subagent_header_on_review() { session_source.clone(), ); - let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled)); let client = ModelClient::new( None, conversation_id, @@ -113,15 +110,7 @@ async fn responses_stream_includes_subagent_header_on_review() { }]; let mut stream = client_session - .stream( - &prompt, - &model_info, - &otel_manager, - effort, - summary, - web_search_eligible, - None, - ) + .stream(&prompt, &model_info, &otel_manager, effort, summary, None) .await .expect("stream failed"); while let Some(event) = stream.next().await { @@ -198,7 +187,6 @@ async fn responses_stream_includes_subagent_header_on_other() { session_source.clone(), ); - let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled)); let client = ModelClient::new( None, conversation_id, @@ -224,15 +212,7 @@ async fn responses_stream_includes_subagent_header_on_other() { }]; let mut stream = client_session - .stream( - &prompt, - &model_info, - &otel_manager, - effort, - summary, - web_search_eligible, - None, - ) + .stream(&prompt, &model_info, &otel_manager, effort, summary, None) .await .expect("stream failed"); while let Some(event) = stream.next().await { @@ -248,66 +228,6 @@ async fn responses_stream_includes_subagent_header_on_other() { ); } -#[tokio::test] -async fn responses_stream_includes_web_search_eligible_header_true_by_default() { - core_test_support::skip_if_no_network!(); - - let server = responses::start_mock_server().await; - let response_body = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_completed("resp-1"), - ]); - - let request_recorder = responses::mount_sse_once_match( - &server, - header(WEB_SEARCH_ELIGIBLE_HEADER, "true"), - response_body, - ) - .await; - - let test = test_codex().build(&server).await.expect("build test codex"); - test.submit_turn("hello").await.expect("submit test prompt"); - - let request = request_recorder.single_request(); - assert_eq!( - request.header(WEB_SEARCH_ELIGIBLE_HEADER).as_deref(), - Some("true") - ); -} - -#[tokio::test] -async fn responses_stream_includes_web_search_eligible_header_false_when_disabled() { - core_test_support::skip_if_no_network!(); - - let server = responses::start_mock_server().await; - let response_body = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_completed("resp-1"), - ]); - - let request_recorder = responses::mount_sse_once_match( - &server, - header(WEB_SEARCH_ELIGIBLE_HEADER, "false"), - response_body, - ) - .await; - - let test = test_codex() - .with_config(|config| { - config.web_search_mode = Some(WebSearchMode::Disabled); - }) - .build(&server) - .await - .expect("build test codex"); - test.submit_turn("hello").await.expect("submit test prompt"); - - let request = request_recorder.single_request(); - assert_eq!( - request.header(WEB_SEARCH_ELIGIBLE_HEADER).as_deref(), - Some("false") - ); -} - #[tokio::test] async fn responses_respects_model_info_overrides_from_config() { core_test_support::skip_if_no_network!(); @@ -368,7 +288,6 @@ async fn responses_respects_model_info_overrides_from_config() { session_source.clone(), ); - let web_search_eligible = !matches!(config.web_search_mode, Some(WebSearchMode::Disabled)); let client = ModelClient::new( None, conversation_id, @@ -394,15 +313,7 @@ async fn responses_respects_model_info_overrides_from_config() { }]; let mut stream = client_session - .stream( - &prompt, - &model_info, - &otel_manager, - effort, - summary, - web_search_eligible, - None, - ) + .stream(&prompt, &model_info, &otel_manager, effort, summary, None) .await .expect("stream failed"); while let Some(event) = stream.next().await { diff --git a/codex-rs/core/tests/suite/client.rs b/codex-rs/core/tests/suite/client.rs index 7debde217f..fafa8b0865 100644 --- a/codex-rs/core/tests/suite/client.rs +++ b/codex-rs/core/tests/suite/client.rs @@ -1340,15 +1340,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() { }); let mut stream = client_session - .stream( - &prompt, - &model_info, - &otel_manager, - effort, - summary, - true, - None, - ) + .stream(&prompt, &model_info, &otel_manager, effort, summary, None) .await .expect("responses stream to start"); diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index f78b7b1a3d..e58a512f9e 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -47,7 +47,6 @@ struct WebsocketTestHarness { model_info: ModelInfo, effort: Option, summary: ReasoningSummary, - web_search_eligible: bool, otel_manager: OtelManager, } @@ -198,7 +197,6 @@ async fn responses_websocket_emits_reasoning_included_event() { &harness.otel_manager, harness.effort, harness.summary, - harness.web_search_eligible, None, ) .await @@ -269,7 +267,6 @@ async fn responses_websocket_emits_rate_limit_events() { &harness.otel_manager, harness.effort, harness.summary, - harness.web_search_eligible, None, ) .await @@ -455,7 +452,6 @@ async fn websocket_harness_with_runtime_metrics( .with_metrics(metrics); let effort = None; let summary = ReasoningSummary::Auto; - let web_search_eligible = true; let client = ModelClient::new( None, conversation_id, @@ -474,7 +470,6 @@ async fn websocket_harness_with_runtime_metrics( model_info, effort, summary, - web_search_eligible, otel_manager, } } @@ -491,7 +486,6 @@ async fn stream_until_complete( &harness.otel_manager, harness.effort, harness.summary, - harness.web_search_eligible, None, ) .await From ddd09a9368b7c0fae99c8475fed43c149b6c4821 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 19:57:57 +0000 Subject: [PATCH 75/90] fix: announcement in prio (#10783) --- codex-rs/tui/src/tooltips.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/codex-rs/tui/src/tooltips.rs b/codex-rs/tui/src/tooltips.rs index b62831e7e6..7365c16496 100644 --- a/codex-rs/tui/src/tooltips.rs +++ b/codex-rs/tui/src/tooltips.rs @@ -39,6 +39,10 @@ fn experimental_tooltips() -> Vec<&'static str> { pub(crate) fn get_tooltip(plan: Option) -> Option { let mut rng = rand::rng(); + if let Some(announcement) = announcement::fetch_announcement_tip() { + return Some(announcement); + } + // Leave small chance for a random tooltip to be shown. if rng.random_ratio(8, 10) { match plan { @@ -56,10 +60,6 @@ pub(crate) fn get_tooltip(plan: Option) -> Option { } } - if let Some(announcement) = announcement::fetch_announcement_tip() { - return Some(announcement); - } - pick_tooltip(&mut rng).map(str::to_string) } From 7e81f636988bc3115fecd63e79c47f6e1979d44c Mon Sep 17 00:00:00 2001 From: Matthew Zeng Date: Thu, 5 Feb 2026 12:04:01 -0800 Subject: [PATCH 76/90] [app-server] Add a method to list experimental features. (#10721) - [x] Add a method to list experimental features. --- .../schema/json/ClientRequest.json | 45 ++++++++ .../codex_app_server_protocol.schemas.json | 107 ++++++++++++++++++ .../v2/ExperimentalFeatureListParams.json | 23 ++++ .../v2/ExperimentalFeatureListResponse.json | 62 ++++++++++ .../schema/typescript/ClientRequest.ts | 3 +- .../typescript/v2/ExperimentalFeature.ts | 29 +++++ .../v2/ExperimentalFeatureListParams.ts | 13 +++ .../v2/ExperimentalFeatureListResponse.ts | 11 ++ .../schema/typescript/v2/index.ts | 3 + .../src/protocol/common.rs | 24 ++++ .../app-server-protocol/src/protocol/v2.rs | 40 +++++++ codex-rs/app-server/README.md | 1 + .../app-server/src/codex_message_processor.rs | 101 +++++++++++++++++ .../app-server/tests/common/mcp_process.rs | 10 ++ .../suite/v2/experimental_feature_list.rs | 66 +++++++++++ codex-rs/app-server/tests/suite/v2/mod.rs | 1 + 16 files changed, 538 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListResponse.ts create mode 100644 codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 54041b40f2..bffc926914 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -422,6 +422,27 @@ ], "type": "object" }, + "ExperimentalFeatureListParams": { + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "type": "object" + }, "FeedbackUploadParams": { "properties": { "classification": { @@ -3562,6 +3583,30 @@ "title": "Model/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "experimentalFeature/list" + ], + "title": "ExperimentalFeature/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExperimentalFeatureListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExperimentalFeature/listRequest", + "type": "object" + }, { "properties": { "id": { 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 9b22ff2207..559da3ffc4 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 @@ -934,6 +934,30 @@ "title": "Model/listRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "experimentalFeature/list" + ], + "title": "ExperimentalFeature/listRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ExperimentalFeatureListParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "ExperimentalFeature/listRequest", + "type": "object" + }, { "properties": { "id": { @@ -11204,6 +11228,89 @@ "title": "ErrorNotification", "type": "object" }, + "ExperimentalFeature": { + "properties": { + "announcement": { + "description": "Announcement copy shown to users when the feature is introduced.", + "type": "string" + }, + "defaultEnabled": { + "description": "Whether this feature is enabled by default.", + "type": "boolean" + }, + "description": { + "description": "Short summary describing what the feature does.", + "type": "string" + }, + "displayName": { + "description": "User-facing display name shown in the experimental features UI.", + "type": "string" + }, + "enabled": { + "description": "Whether this feature is currently enabled in the loaded config.", + "type": "boolean" + }, + "flagName": { + "description": "Stable key used in config.toml and CLI flag toggles.", + "type": "string" + } + }, + "required": [ + "announcement", + "defaultEnabled", + "description", + "displayName", + "enabled", + "flagName" + ], + "type": "object" + }, + "ExperimentalFeatureListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ExperimentalFeatureListParams", + "type": "object" + }, + "ExperimentalFeatureListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/ExperimentalFeature" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ExperimentalFeatureListResponse", + "type": "object" + }, "FeedbackUploadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListParams.json b/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListParams.json new file mode 100644 index 0000000000..ab562edbf2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListParams.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "format": "uint32", + "minimum": 0.0, + "type": [ + "integer", + "null" + ] + } + }, + "title": "ExperimentalFeatureListParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json new file mode 100644 index 0000000000..b9e36760bc --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ExperimentalFeature": { + "properties": { + "announcement": { + "description": "Announcement copy shown to users when the feature is introduced.", + "type": "string" + }, + "defaultEnabled": { + "description": "Whether this feature is enabled by default.", + "type": "boolean" + }, + "description": { + "description": "Short summary describing what the feature does.", + "type": "string" + }, + "displayName": { + "description": "User-facing display name shown in the experimental features UI.", + "type": "string" + }, + "enabled": { + "description": "Whether this feature is currently enabled in the loaded config.", + "type": "boolean" + }, + "flagName": { + "description": "Stable key used in config.toml and CLI flag toggles.", + "type": "string" + } + }, + "required": [ + "announcement", + "defaultEnabled", + "description", + "displayName", + "enabled", + "flagName" + ], + "type": "object" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/ExperimentalFeature" + }, + "type": "array" + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "data" + ], + "title": "ExperimentalFeatureListResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index 176f86be56..04d52f03bc 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -27,6 +27,7 @@ import type { CommandExecParams } from "./v2/CommandExecParams"; import type { ConfigBatchWriteParams } from "./v2/ConfigBatchWriteParams"; import type { ConfigReadParams } from "./v2/ConfigReadParams"; import type { ConfigValueWriteParams } from "./v2/ConfigValueWriteParams"; +import type { ExperimentalFeatureListParams } from "./v2/ExperimentalFeatureListParams"; import type { FeedbackUploadParams } from "./v2/FeedbackUploadParams"; import type { GetAccountParams } from "./v2/GetAccountParams"; import type { ListMcpServerStatusParams } from "./v2/ListMcpServerStatusParams"; @@ -55,4 +56,4 @@ import type { TurnStartParams } from "./v2/TurnStartParams"; /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts new file mode 100644 index 0000000000..6275148780 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts @@ -0,0 +1,29 @@ +// 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 ExperimentalFeature = { +/** + * Stable key used in config.toml and CLI flag toggles. + */ +flagName: string, +/** + * User-facing display name shown in the experimental features UI. + */ +displayName: string, +/** + * Short summary describing what the feature does. + */ +description: string, +/** + * Announcement copy shown to users when the feature is introduced. + */ +announcement: string, +/** + * Whether this feature is currently enabled in the loaded config. + */ +enabled: boolean, +/** + * Whether this feature is enabled by default. + */ +defaultEnabled: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListParams.ts new file mode 100644 index 0000000000..1d4dc84e0d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListParams.ts @@ -0,0 +1,13 @@ +// 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 ExperimentalFeatureListParams = { +/** + * Opaque pagination cursor returned by a previous call. + */ +cursor?: string | null, +/** + * Optional page size; defaults to a reasonable server-side value. + */ +limit?: number | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListResponse.ts new file mode 100644 index 0000000000..46b39ba019 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureListResponse.ts @@ -0,0 +1,11 @@ +// 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 { ExperimentalFeature } from "./ExperimentalFeature"; + +export type ExperimentalFeatureListResponse = { data: Array, +/** + * Opaque cursor to pass to the next call to continue after the last item. + * If None, there are no more items to return. + */ +nextCursor: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index ec5d54cba1..dfbdc58acb 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -52,6 +52,9 @@ export type { DynamicToolCallResponse } from "./DynamicToolCallResponse"; export type { DynamicToolSpec } from "./DynamicToolSpec"; export type { ErrorNotification } from "./ErrorNotification"; export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; +export type { ExperimentalFeature } from "./ExperimentalFeature"; +export type { ExperimentalFeatureListParams } from "./ExperimentalFeatureListParams"; +export type { ExperimentalFeatureListResponse } from "./ExperimentalFeatureListResponse"; export type { FeedbackUploadParams } from "./FeedbackUploadParams"; export type { FeedbackUploadResponse } from "./FeedbackUploadResponse"; export type { FileChangeApprovalDecision } from "./FileChangeApprovalDecision"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index c84d28ae89..912f4d58ac 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -265,6 +265,10 @@ client_request_definitions! { params: v2::ModelListParams, response: v2::ModelListResponse, }, + ExperimentalFeatureList => "experimentalFeature/list" { + params: v2::ExperimentalFeatureListParams, + response: v2::ExperimentalFeatureListResponse, + }, #[experimental("collaborationMode/list")] /// Lists collaboration mode presets. CollaborationModeList => "collaborationMode/list" { @@ -1087,6 +1091,26 @@ mod tests { Ok(()) } + #[test] + fn serialize_list_experimental_features() -> Result<()> { + let request = ClientRequest::ExperimentalFeatureList { + request_id: RequestId::Integer(8), + params: v2::ExperimentalFeatureListParams::default(), + }; + assert_eq!( + json!({ + "method": "experimentalFeature/list", + "id": 8, + "params": { + "cursor": null, + "limit": null + } + }), + serde_json::to_value(&request)?, + ); + Ok(()) + } + #[test] fn mock_experimental_method_is_marked_experimental() { let request = ClientRequest::MockExperimentalMethod { diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index baafa84677..73d664016f 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1043,6 +1043,46 @@ pub struct CollaborationModeListResponse { pub data: Vec, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ExperimentalFeatureListParams { + /// Opaque pagination cursor returned by a previous call. + #[ts(optional = nullable)] + pub cursor: Option, + /// Optional page size; defaults to a reasonable server-side value. + #[ts(optional = nullable)] + pub limit: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ExperimentalFeature { + /// Stable key used in config.toml and CLI flag toggles. + pub flag_name: String, + /// User-facing display name shown in the experimental features UI. + pub display_name: String, + /// Short summary describing what the feature does. + pub description: String, + /// Announcement copy shown to users when the feature is introduced. + pub announcement: String, + /// Whether this feature is currently enabled in the loaded config. + pub enabled: bool, + /// Whether this feature is enabled by default. + pub default_enabled: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ExperimentalFeatureListResponse { + pub data: Vec, + /// Opaque cursor to pass to the next call to continue after the last item. + /// If None, there are no more items to return. + pub next_cursor: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 4df8399e88..231e50b1a1 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -92,6 +92,7 @@ Example (from OpenAI's official VSCode extension): - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). - `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids). +- `experimentalFeature/list` — list experimental feature flags with metadata (flag name, display name, description, announcement, enabled/default-enabled) and cursor pagination. - `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). - `skills/list` — list skills for one or more `cwd` values (optional `forceReload`). - `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**). diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index ea60d20877..f681f4a3d7 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -32,6 +32,9 @@ use codex_app_server_protocol::ConversationGitInfo; use codex_app_server_protocol::ConversationSummary; use codex_app_server_protocol::DynamicToolSpec as ApiDynamicToolSpec; use codex_app_server_protocol::ExecOneOffCommandResponse; +use codex_app_server_protocol::ExperimentalFeature as ApiExperimentalFeature; +use codex_app_server_protocol::ExperimentalFeatureListParams; +use codex_app_server_protocol::ExperimentalFeatureListResponse; use codex_app_server_protocol::FeedbackUploadParams; use codex_app_server_protocol::FeedbackUploadResponse; use codex_app_server_protocol::ForkConversationParams; @@ -168,7 +171,9 @@ use codex_core::default_client::set_default_client_residency_requirement; use codex_core::error::CodexErr; use codex_core::exec::ExecParams; use codex_core::exec_env::create_env; +use codex_core::features::FEATURES; use codex_core::features::Feature; +use codex_core::features::Stage; use codex_core::find_archived_thread_path_by_id_str; use codex_core::find_thread_path_by_id_str; use codex_core::git_info::git_diff_to_remote; @@ -530,6 +535,9 @@ impl CodexMessageProcessor { Self::list_models(outgoing, thread_manager, config, request_id, params).await; }); } + ClientRequest::ExperimentalFeatureList { request_id, params } => { + self.experimental_feature_list(request_id, params).await; + } ClientRequest::CollaborationModeList { request_id, params } => { let outgoing = self.outgoing.clone(); let thread_manager = self.thread_manager.clone(); @@ -3134,6 +3142,99 @@ impl CodexMessageProcessor { outgoing.send_response(request_id, response).await; } + async fn experimental_feature_list( + &self, + request_id: RequestId, + params: ExperimentalFeatureListParams, + ) { + let ExperimentalFeatureListParams { cursor, limit } = params; + let config = match self.load_latest_config().await { + Ok(config) => config, + Err(error) => { + self.outgoing.send_error(request_id, error).await; + return; + } + }; + + let data = FEATURES + .iter() + .filter_map(|spec| { + let Stage::Experimental { + name, + menu_description, + announcement, + } = spec.stage + else { + return None; + }; + Some(ApiExperimentalFeature { + flag_name: spec.key.to_string(), + display_name: name.to_string(), + description: menu_description.to_string(), + announcement: announcement.to_string(), + enabled: config.features.enabled(spec.id), + default_enabled: spec.default_enabled, + }) + }) + .collect::>(); + + let total = data.len(); + if total == 0 { + self.outgoing + .send_response( + request_id, + ExperimentalFeatureListResponse { + data: Vec::new(), + next_cursor: None, + }, + ) + .await; + return; + } + + // Clamp to 1 so limit=0 cannot return a non-advancing page. + let effective_limit = limit.unwrap_or(total as u32).max(1) as usize; + let effective_limit = effective_limit.min(total); + let start = match cursor { + Some(cursor) => match cursor.parse::() { + Ok(idx) => idx, + Err(_) => { + self.send_invalid_request_error( + request_id, + format!("invalid cursor: {cursor}"), + ) + .await; + return; + } + }, + None => 0, + }; + + if start > total { + self.send_invalid_request_error( + request_id, + format!("cursor {start} exceeds total experimental features {total}"), + ) + .await; + return; + } + + let end = start.saturating_add(effective_limit).min(total); + let data = data[start..end].to_vec(); + let next_cursor = if end < total { + Some(end.to_string()) + } else { + None + }; + + self.outgoing + .send_response( + request_id, + ExperimentalFeatureListResponse { data, next_cursor }, + ) + .await; + } + async fn mock_experimental_method( &self, request_id: RequestId, diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index ba6dc058e6..d992db6d45 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -22,6 +22,7 @@ use codex_app_server_protocol::CollaborationModeListParams; use codex_app_server_protocol::ConfigBatchWriteParams; use codex_app_server_protocol::ConfigReadParams; use codex_app_server_protocol::ConfigValueWriteParams; +use codex_app_server_protocol::ExperimentalFeatureListParams; use codex_app_server_protocol::FeedbackUploadParams; use codex_app_server_protocol::ForkConversationParams; use codex_app_server_protocol::GetAccountParams; @@ -473,6 +474,15 @@ impl McpProcess { self.send_request("model/list", params).await } + /// Send an `experimentalFeature/list` JSON-RPC request. + pub async fn send_experimental_feature_list_request( + &mut self, + params: ExperimentalFeatureListParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("experimentalFeature/list", params).await + } + /// Send an `app/list` JSON-RPC request. pub async fn send_apps_list_request(&mut self, params: AppsListParams) -> anyhow::Result { let params = Some(serde_json::to_value(params)?); diff --git a/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs b/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs new file mode 100644 index 0000000000..2ca236d89e --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs @@ -0,0 +1,66 @@ +use std::time::Duration; + +use anyhow::Result; +use app_test_support::McpProcess; +use app_test_support::to_response; +use codex_app_server_protocol::ExperimentalFeature; +use codex_app_server_protocol::ExperimentalFeatureListParams; +use codex_app_server_protocol::ExperimentalFeatureListResponse; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::RequestId; +use codex_core::features::FEATURES; +use codex_core::features::Stage; +use pretty_assertions::assert_eq; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); + +#[tokio::test] +async fn experimental_feature_list_returns_experimental_feature_metadata() -> Result<()> { + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + + timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_experimental_feature_list_request(ExperimentalFeatureListParams::default()) + .await?; + + let response: JSONRPCResponse = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + + let actual = to_response::(response)?; + let expected_data = FEATURES + .iter() + .filter_map(|spec| { + let Stage::Experimental { + name, + menu_description, + announcement, + } = spec.stage + else { + return None; + }; + + Some(ExperimentalFeature { + flag_name: spec.key.to_string(), + display_name: name.to_string(), + description: menu_description.to_string(), + announcement: announcement.to_string(), + enabled: spec.default_enabled, + default_enabled: spec.default_enabled, + }) + }) + .collect::>(); + let expected = ExperimentalFeatureListResponse { + data: expected_data, + next_cursor: None, + }; + + assert_eq!(actual, expected); + Ok(()) +} diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 3a84e48b9e..2130c6e896 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -6,6 +6,7 @@ mod compaction; mod config_rpc; mod dynamic_tools; mod experimental_api; +mod experimental_feature_list; mod initialize; mod model_list; mod output_schema; From 5602edc1d06d05ab6311b83456071e07a48a0dcf Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Thu, 5 Feb 2026 12:11:32 -0800 Subject: [PATCH 77/90] chore: limit update to 0.98.0 NUX to < 0.98.0 ver (#10787) seems like footgun if we forget to remove before releasing 0.99.0, limited announcement to versions < 0.98.0 --- announcement_tip.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/announcement_tip.toml b/announcement_tip.toml index b0fdfd2c4c..e9bd9ef19f 100644 --- a/announcement_tip.toml +++ b/announcement_tip.toml @@ -20,3 +20,4 @@ to_date = "2026-05-10" content = "**BREAKING NEWS**: `gpt-5.3-codex` is out! Upgrade to `0.98.0` to try it." from_date = "2026-02-01" to_date = "2026-02-15" +version_regex = "^0\\.(?:[0-9]|[1-8][0-9]|9[0-7])\\." From 529b53956408fcc87a75922185baf604d7f96e0d Mon Sep 17 00:00:00 2001 From: pap-openai Date: Thu, 5 Feb 2026 20:18:29 +0000 Subject: [PATCH 78/90] Add analytics for /rename and /fork (#10655) --- codex-rs/tui/src/app.rs | 3 +++ codex-rs/tui/src/chatwidget.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 8ef4283c6b..718615237d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1056,6 +1056,7 @@ impl App { ChatWidget::new_from_existing(init, resumed.thread, resumed.session_configured) } SessionSelection::Fork(path) => { + otel_manager.counter("codex.thread.fork", 1, &[("source", "cli_subcommand")]); let forked = thread_manager .fork_thread(usize::MAX, config.clone(), path.clone()) .await @@ -1433,6 +1434,8 @@ impl App { tui.frame_requester().schedule_frame(); } AppEvent::ForkCurrentSession => { + self.otel_manager + .counter("codex.thread.fork", 1, &[("source", "slash_command")]); let summary = session_summary( self.chat_widget.token_usage(), self.chat_widget.thread_id(), diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 4b260c67eb..93d21610d3 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -3123,6 +3123,7 @@ impl ChatWidget { self.open_review_popup(); } SlashCommand::Rename => { + self.otel_manager.counter("codex.thread.rename", 1, &[]); self.show_rename_prompt(); } SlashCommand::Model => { @@ -3342,6 +3343,7 @@ impl ChatWidget { let trimmed = args.trim(); match cmd { SlashCommand::Rename if !trimmed.is_empty() => { + self.otel_manager.counter("codex.thread.rename", 1, &[]); let Some((prepared_args, _prepared_elements)) = self.bottom_pane.prepare_inline_args_submission(false) else { From 428a9f6035bf025e622fe62929a30cd2ecbffa20 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 20:45:16 +0000 Subject: [PATCH 79/90] feat: wait for backfill to be ready (#10790) --- codex-rs/core/src/rollout/tests.rs | 8 +++ codex-rs/core/src/state_db.rs | 53 ++++++++++++------- .../core/tests/suite/rollout_list_find.rs | 1 + 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/codex-rs/core/src/rollout/tests.rs b/codex-rs/core/src/rollout/tests.rs index ccd476b8cf..a4bd72433e 100644 --- a/codex-rs/core/src/rollout/tests.rs +++ b/codex-rs/core/src/rollout/tests.rs @@ -62,6 +62,10 @@ async fn insert_state_db_thread( codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string(), None) .await .expect("state db should initialize"); + runtime + .mark_backfill_complete(None) + .await + .expect("backfill should be complete"); let created_at = chrono::Utc .with_ymd_and_hms(2025, 1, 3, 12, 0, 0) .single() @@ -280,6 +284,10 @@ async fn find_thread_path_repairs_missing_db_row_after_filesystem_fallback() { codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string(), None) .await .expect("state db should initialize"); + _runtime + .mark_backfill_complete(None) + .await + .expect("backfill should be complete"); let found = crate::rollout::find_thread_path_by_id_str(home, &uuid.to_string()) .await diff --git a/codex-rs/core/src/state_db.rs b/codex-rs/core/src/state_db.rs index 97846cdcf0..ed2e8c2640 100644 --- a/codex-rs/core/src/state_db.rs +++ b/codex-rs/core/src/state_db.rs @@ -54,30 +54,20 @@ pub(crate) async fn init_if_enabled( return None; } }; - let should_backfill = match runtime.get_backfill_state().await { - Ok(state) => state.status != codex_state::BackfillStatus::Complete, + let backfill_state = match runtime.get_backfill_state().await { + Ok(state) => state, Err(err) => { warn!( "failed to read backfill state at {}: {err}", config.codex_home.display() ); - true + return None; } }; - if should_backfill { - let runtime_for_backfill = Arc::clone(&runtime); - let config_for_backfill = config.clone(); - let otel_for_backfill = otel.cloned(); - tokio::task::spawn(async move { - metadata::backfill_sessions( - runtime_for_backfill.as_ref(), - &config_for_backfill, - otel_for_backfill.as_ref(), - ) - .await; - }); + if backfill_state.status != codex_state::BackfillStatus::Complete { + metadata::backfill_sessions(runtime.as_ref(), config, otel).await; } - Some(runtime) + require_backfill_complete(runtime, config.codex_home.as_path()).await } /// Get the DB if the feature is enabled and the DB exists. @@ -88,13 +78,14 @@ pub async fn get_state_db(config: &Config, otel: Option<&OtelManager>) -> Option { return None; } - codex_state::StateRuntime::init( + let runtime = codex_state::StateRuntime::init( config.codex_home.clone(), config.model_provider_id.clone(), otel.cloned(), ) .await - .ok() + .ok()?; + require_backfill_complete(runtime, config.codex_home.as_path()).await } /// Open the state runtime when the SQLite file exists, without feature gating. @@ -112,7 +103,31 @@ pub async fn open_if_present(codex_home: &Path, default_provider: &str) -> Optio ) .await .ok()?; - Some(runtime) + require_backfill_complete(runtime, codex_home).await +} + +async fn require_backfill_complete( + runtime: StateDbHandle, + codex_home: &Path, +) -> Option { + match runtime.get_backfill_state().await { + Ok(state) if state.status == codex_state::BackfillStatus::Complete => Some(runtime), + Ok(state) => { + warn!( + "state db backfill not complete at {} (status: {})", + codex_home.display(), + state.status.as_str() + ); + None + } + Err(err) => { + warn!( + "failed to read backfill state at {}: {err}", + codex_home.display() + ); + None + } + } } fn cursor_to_anchor(cursor: Option<&Cursor>) -> Option { diff --git a/codex-rs/core/tests/suite/rollout_list_find.rs b/codex-rs/core/tests/suite/rollout_list_find.rs index 5b545384b9..236a70279b 100644 --- a/codex-rs/core/tests/suite/rollout_list_find.rs +++ b/codex-rs/core/tests/suite/rollout_list_find.rs @@ -59,6 +59,7 @@ async fn upsert_thread_metadata(codex_home: &Path, thread_id: ThreadId, rollout_ let runtime = StateRuntime::init(codex_home.to_path_buf(), "test-provider".to_string(), None) .await .unwrap(); + runtime.mark_backfill_complete(None).await.unwrap(); let mut builder = ThreadMetadataBuilder::new( thread_id, rollout_path, From 8473096efbcb168c6b036d1a22432fc41408d209 Mon Sep 17 00:00:00 2001 From: Max Johnson <162359438+maxj-oai@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:56:34 -0800 Subject: [PATCH 80/90] Add app-server transport layer with websocket support (#10693) - Adds --listen to codex app-server with two listen modes: - stdio:// (default, existing behavior) - ws://IP:PORT (new websocket transport) - Refactors message routing to be connection-aware: - Tracks per-connection session state (initialize/experimental capability) - Routes responses/errors to the originating connection - Broadcasts server notifications/requests to initialized connections - Updates initialization semantics to be per connection (not process-global), and updates app-server docs accordingly. - Adds websocket accept/read/write handling (JSON-RPC per text frame, ping/pong handling, connection lifecycle events). Testing - Unit tests for transport URL parsing and targeted response/error routing. - New websocket integration test validating: - per-connection initialization requirements - no cross-connection response leakage - same request IDs on different connections route independently. --- codex-rs/Cargo.lock | 4 + codex-rs/app-server/Cargo.toml | 5 + codex-rs/app-server/README.md | 13 +- .../app-server/src/bespoke_event_handling.rs | 70 ++- .../app-server/src/codex_message_processor.rs | 402 +++++++++++------ codex-rs/app-server/src/lib.rs | 172 ++++--- codex-rs/app-server/src/main.rs | 21 +- codex-rs/app-server/src/message_processor.rs | 121 +++-- codex-rs/app-server/src/outgoing_message.rs | 168 ++++++- codex-rs/app-server/src/transport.rs | 424 ++++++++++++++++++ .../suite/v2/connection_handling_websocket.rs | 263 +++++++++++ codex-rs/app-server/tests/suite/v2/mod.rs | 1 + codex-rs/cli/src/main.rs | 47 +- 13 files changed, 1403 insertions(+), 308 deletions(-) create mode 100644 codex-rs/app-server/src/transport.rs create mode 100644 codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index e90b25ee9e..24f89c0c5a 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1219,6 +1219,7 @@ dependencies = [ "axum", "base64 0.22.1", "chrono", + "clap", "codex-app-server-protocol", "codex-arg0", "codex-backend-client", @@ -1233,8 +1234,10 @@ dependencies = [ "codex-protocol", "codex-rmcp-client", "codex-utils-absolute-path", + "codex-utils-cargo-bin", "codex-utils-json-to-toml", "core_test_support", + "futures", "os_info", "pretty_assertions", "rmcp", @@ -1245,6 +1248,7 @@ dependencies = [ "tempfile", "time", "tokio", + "tokio-tungstenite", "toml 0.9.11+spec-1.1.0", "tracing", "tracing-subscriber", diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 778d57bab0..2f6011a634 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -33,6 +33,8 @@ codex-rmcp-client = { workspace = true } codex-utils-absolute-path = { workspace = true } codex-utils-json-to-toml = { workspace = true } chrono = { workspace = true } +clap = { workspace = true, features = ["derive"] } +futures = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tempfile = { workspace = true } @@ -45,6 +47,7 @@ tokio = { workspace = true, features = [ "rt-multi-thread", "signal", ] } +tokio-tungstenite = { workspace = true } tracing = { workspace = true, features = ["log"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } uuid = { workspace = true, features = ["serde", "v7"] } @@ -59,6 +62,7 @@ axum = { workspace = true, default-features = false, features = [ base64 = { workspace = true } codex-execpolicy = { workspace = true } core_test_support = { workspace = true } +codex-utils-cargo-bin = { workspace = true } os_info = { workspace = true } pretty_assertions = { workspace = true } rmcp = { workspace = true, default-features = false, features = [ @@ -66,5 +70,6 @@ rmcp = { workspace = true, default-features = false, features = [ "transport-streamable-http-server", ] } serial_test = { workspace = true } +tokio-tungstenite = { workspace = true } wiremock = { workspace = true } shlex = { workspace = true } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 231e50b1a1..3236cb0eef 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -19,7 +19,14 @@ ## Protocol -Similar to [MCP](https://modelcontextprotocol.io/), `codex app-server` supports bidirectional communication, streaming JSONL over stdio. The protocol is JSON-RPC 2.0, though the `"jsonrpc":"2.0"` header is omitted. +Similar to [MCP](https://modelcontextprotocol.io/), `codex app-server` supports bidirectional communication using JSON-RPC 2.0 messages (with the `"jsonrpc":"2.0"` header omitted on the wire). + +Supported transports: + +- stdio (`--listen stdio://`, default): newline-delimited JSON (JSONL) +- websocket (`--listen ws://IP:PORT`): one JSON-RPC message per websocket text frame (**experimental / unsupported**) + +Websocket transport is currently experimental and unsupported. Do not rely on it for production workloads. ## Message Schema @@ -42,7 +49,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat ## Lifecycle Overview -- Initialize once: Immediately after launching the codex app-server process, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request before this handshake gets rejected. +- Initialize once per connection: Immediately after opening a transport connection, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request on that connection before this handshake gets rejected. - Start (or resume) a thread: Call `thread/start` to open a fresh conversation. The response returns the thread object and you’ll also get a `thread/started` notification. If you’re continuing an existing conversation, call `thread/resume` with its ID instead. If you want to branch from an existing conversation, call `thread/fork` to create a new thread id with copied history. - Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy, etc. This immediately returns the new turn object and triggers a `turn/started` notification. - Stream events: After `turn/start`, keep reading JSON-RPC notifications on stdout. You’ll see `item/started`, `item/completed`, deltas like `item/agentMessage/delta`, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes). @@ -50,7 +57,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat ## Initialization -Clients must send a single `initialize` request before invoking any other method, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls receive an `"Already initialized"` error. +Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls on the same connection receive an `"Already initialized"` error. Applications building on top of `codex app-server` should identify themselves via the `clientInfo` parameter. diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index fd8db9cb62..1d3d7ff059 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -1093,7 +1093,7 @@ pub(crate) async fn apply_bespoke_event_handling( ), data: None, }; - outgoing.send_error(request_id, error).await; + outgoing.send_error(request_id.clone(), error).await; return; } } @@ -1107,7 +1107,7 @@ pub(crate) async fn apply_bespoke_event_handling( ), data: None, }; - outgoing.send_error(request_id, error).await; + outgoing.send_error(request_id.clone(), error).await; return; } }; @@ -1831,6 +1831,7 @@ async fn construct_mcp_tool_call_end_notification( mod tests { use super::*; use crate::CHANNEL_CAPACITY; + use crate::outgoing_message::OutgoingEnvelope; use crate::outgoing_message::OutgoingMessage; use crate::outgoing_message::OutgoingMessageSender; use anyhow::Result; @@ -1858,6 +1859,21 @@ mod tests { Arc::new(Mutex::new(HashMap::new())) } + async fn recv_broadcast_message( + rx: &mut mpsc::Receiver, + ) -> Result { + let envelope = rx + .recv() + .await + .ok_or_else(|| anyhow!("should send one message"))?; + match envelope { + OutgoingEnvelope::Broadcast { message } => Ok(message), + OutgoingEnvelope::ToConnection { connection_id, .. } => { + bail!("unexpected targeted message for connection {connection_id:?}") + } + } + } + #[test] fn file_change_accept_for_session_maps_to_approved_for_session() { let (decision, completion_status) = @@ -1910,10 +1926,7 @@ mod tests { ) .await; - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send one notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnCompleted(n)) => { assert_eq!(n.turn.id, event_turn_id); @@ -1952,10 +1965,7 @@ mod tests { ) .await; - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send one notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnCompleted(n)) => { assert_eq!(n.turn.id, event_turn_id); @@ -1994,10 +2004,7 @@ mod tests { ) .await; - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send one notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnCompleted(n)) => { assert_eq!(n.turn.id, event_turn_id); @@ -2046,10 +2053,7 @@ mod tests { ) .await; - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send one notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnPlanUpdated(n)) => { assert_eq!(n.thread_id, conversation_id.to_string()); @@ -2117,10 +2121,7 @@ mod tests { ) .await; - let first = rx - .recv() - .await - .ok_or_else(|| anyhow!("expected usage notification"))?; + let first = recv_broadcast_message(&mut rx).await?; match first { OutgoingMessage::AppServerNotification( ServerNotification::ThreadTokenUsageUpdated(payload), @@ -2136,10 +2137,7 @@ mod tests { other => bail!("unexpected notification: {other:?}"), } - let second = rx - .recv() - .await - .ok_or_else(|| anyhow!("expected rate limit notification"))?; + let second = recv_broadcast_message(&mut rx).await?; match second { OutgoingMessage::AppServerNotification( ServerNotification::AccountRateLimitsUpdated(payload), @@ -2276,10 +2274,7 @@ mod tests { .await; // Verify: A turn 1 - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send first notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnCompleted(n)) => { assert_eq!(n.turn.id, a_turn1); @@ -2297,10 +2292,7 @@ mod tests { } // Verify: B turn 1 - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send second notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnCompleted(n)) => { assert_eq!(n.turn.id, b_turn1); @@ -2318,10 +2310,7 @@ mod tests { } // Verify: A turn 2 - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send third notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnCompleted(n)) => { assert_eq!(n.turn.id, a_turn2); @@ -2487,10 +2476,7 @@ mod tests { ) .await; - let msg = rx - .recv() - .await - .ok_or_else(|| anyhow!("should send one notification"))?; + let msg = recv_broadcast_message(&mut rx).await?; match msg { OutgoingMessage::AppServerNotification(ServerNotification::TurnDiffUpdated( notification, diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index f681f4a3d7..afcee13af9 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -3,6 +3,8 @@ use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::fuzzy_file_search::run_fuzzy_file_search; use crate::models::supported_models; +use crate::outgoing_message::ConnectionId; +use crate::outgoing_message::ConnectionRequestId; use crate::outgoing_message::OutgoingMessageSender; use crate::outgoing_message::OutgoingNotification; use chrono::DateTime; @@ -80,7 +82,6 @@ use codex_app_server_protocol::NewConversationParams; use codex_app_server_protocol::NewConversationResponse; use codex_app_server_protocol::RemoveConversationListenerParams; use codex_app_server_protocol::RemoveConversationSubscriptionResponse; -use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ResumeConversationParams; use codex_app_server_protocol::ResumeConversationResponse; use codex_app_server_protocol::ReviewDelivery as ApiReviewDelivery; @@ -244,10 +245,10 @@ use uuid::Uuid; use crate::filters::compute_source_filters; use crate::filters::source_kind_matches; -type PendingInterruptQueue = Vec<(RequestId, ApiVersion)>; +type PendingInterruptQueue = Vec<(ConnectionRequestId, ApiVersion)>; pub(crate) type PendingInterrupts = Arc>>; -pub(crate) type PendingRollbacks = Arc>>; +pub(crate) type PendingRollbacks = Arc>>; /// Per-conversation accumulation of the latest states e.g. error message while a turn runs. #[derive(Default, Clone)] @@ -451,96 +452,126 @@ impl CodexMessageProcessor { Ok((review_request, hint)) } - pub async fn process_request(&mut self, request: ClientRequest) { + pub async fn process_request(&mut self, connection_id: ConnectionId, request: ClientRequest) { + let to_connection_request_id = |request_id| ConnectionRequestId { + connection_id, + request_id, + }; + match request { ClientRequest::Initialize { .. } => { panic!("Initialize should be handled in MessageProcessor"); } // === v2 Thread/Turn APIs === ClientRequest::ThreadStart { request_id, params } => { - self.thread_start(request_id, params).await; + self.thread_start(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadResume { request_id, params } => { - self.thread_resume(request_id, params).await; + self.thread_resume(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadFork { request_id, params } => { - self.thread_fork(request_id, params).await; + self.thread_fork(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadArchive { request_id, params } => { - self.thread_archive(request_id, params).await; + self.thread_archive(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadSetName { request_id, params } => { - self.thread_set_name(request_id, params).await; + self.thread_set_name(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadUnarchive { request_id, params } => { - self.thread_unarchive(request_id, params).await; + self.thread_unarchive(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadCompactStart { request_id, params } => { - self.thread_compact_start(request_id, params).await; + self.thread_compact_start(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadRollback { request_id, params } => { - self.thread_rollback(request_id, params).await; + self.thread_rollback(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadList { request_id, params } => { - self.thread_list(request_id, params).await; + self.thread_list(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadLoadedList { request_id, params } => { - self.thread_loaded_list(request_id, params).await; + self.thread_loaded_list(to_connection_request_id(request_id), params) + .await; } ClientRequest::ThreadRead { request_id, params } => { - self.thread_read(request_id, params).await; + self.thread_read(to_connection_request_id(request_id), params) + .await; } ClientRequest::SkillsList { request_id, params } => { - self.skills_list(request_id, params).await; + self.skills_list(to_connection_request_id(request_id), params) + .await; } ClientRequest::SkillsRemoteRead { request_id, params } => { - self.skills_remote_read(request_id, params).await; + self.skills_remote_read(to_connection_request_id(request_id), params) + .await; } ClientRequest::SkillsRemoteWrite { request_id, params } => { - self.skills_remote_write(request_id, params).await; + self.skills_remote_write(to_connection_request_id(request_id), params) + .await; } ClientRequest::AppsList { request_id, params } => { - self.apps_list(request_id, params).await; + self.apps_list(to_connection_request_id(request_id), params) + .await; } ClientRequest::SkillsConfigWrite { request_id, params } => { - self.skills_config_write(request_id, params).await; + self.skills_config_write(to_connection_request_id(request_id), params) + .await; } ClientRequest::TurnStart { request_id, params } => { - self.turn_start(request_id, params).await; + self.turn_start(to_connection_request_id(request_id), params) + .await; } ClientRequest::TurnInterrupt { request_id, params } => { - self.turn_interrupt(request_id, params).await; + self.turn_interrupt(to_connection_request_id(request_id), params) + .await; } ClientRequest::ReviewStart { request_id, params } => { - self.review_start(request_id, params).await; + self.review_start(to_connection_request_id(request_id), params) + .await; } ClientRequest::NewConversation { request_id, params } => { // Do not tokio::spawn() to process new_conversation() // asynchronously because we need to ensure the conversation is // created before processing any subsequent messages. - self.process_new_conversation(request_id, params).await; + self.process_new_conversation(to_connection_request_id(request_id), params) + .await; } ClientRequest::GetConversationSummary { request_id, params } => { - self.get_thread_summary(request_id, params).await; + self.get_thread_summary(to_connection_request_id(request_id), params) + .await; } ClientRequest::ListConversations { request_id, params } => { - self.handle_list_conversations(request_id, params).await; + self.handle_list_conversations(to_connection_request_id(request_id), params) + .await; } ClientRequest::ModelList { request_id, params } => { let outgoing = self.outgoing.clone(); let thread_manager = self.thread_manager.clone(); let config = self.config.clone(); + let request_id = to_connection_request_id(request_id); tokio::spawn(async move { Self::list_models(outgoing, thread_manager, config, request_id, params).await; }); } ClientRequest::ExperimentalFeatureList { request_id, params } => { - self.experimental_feature_list(request_id, params).await; + self.experimental_feature_list(to_connection_request_id(request_id), params) + .await; } ClientRequest::CollaborationModeList { request_id, params } => { let outgoing = self.outgoing.clone(); let thread_manager = self.thread_manager.clone(); + let request_id = to_connection_request_id(request_id); tokio::spawn(async move { Self::list_collaboration_modes(outgoing, thread_manager, request_id, params) @@ -548,109 +579,136 @@ impl CodexMessageProcessor { }); } ClientRequest::MockExperimentalMethod { request_id, params } => { - self.mock_experimental_method(request_id, params).await; + self.mock_experimental_method(to_connection_request_id(request_id), params) + .await; } ClientRequest::McpServerOauthLogin { request_id, params } => { - self.mcp_server_oauth_login(request_id, params).await; + self.mcp_server_oauth_login(to_connection_request_id(request_id), params) + .await; } ClientRequest::McpServerRefresh { request_id, params } => { - self.mcp_server_refresh(request_id, params).await; + self.mcp_server_refresh(to_connection_request_id(request_id), params) + .await; } ClientRequest::McpServerStatusList { request_id, params } => { - self.list_mcp_server_status(request_id, params).await; + self.list_mcp_server_status(to_connection_request_id(request_id), params) + .await; } ClientRequest::LoginAccount { request_id, params } => { - self.login_v2(request_id, params).await; + self.login_v2(to_connection_request_id(request_id), params) + .await; } ClientRequest::LogoutAccount { request_id, params: _, } => { - self.logout_v2(request_id).await; + self.logout_v2(to_connection_request_id(request_id)).await; } ClientRequest::CancelLoginAccount { request_id, params } => { - self.cancel_login_v2(request_id, params).await; + self.cancel_login_v2(to_connection_request_id(request_id), params) + .await; } ClientRequest::GetAccount { request_id, params } => { - self.get_account(request_id, params).await; + self.get_account(to_connection_request_id(request_id), params) + .await; } ClientRequest::ResumeConversation { request_id, params } => { - self.handle_resume_conversation(request_id, params).await; + self.handle_resume_conversation(to_connection_request_id(request_id), params) + .await; } ClientRequest::ForkConversation { request_id, params } => { - self.handle_fork_conversation(request_id, params).await; + self.handle_fork_conversation(to_connection_request_id(request_id), params) + .await; } ClientRequest::ArchiveConversation { request_id, params } => { - self.archive_conversation(request_id, params).await; + self.archive_conversation(to_connection_request_id(request_id), params) + .await; } ClientRequest::SendUserMessage { request_id, params } => { - self.send_user_message(request_id, params).await; + self.send_user_message(to_connection_request_id(request_id), params) + .await; } ClientRequest::SendUserTurn { request_id, params } => { - self.send_user_turn(request_id, params).await; + self.send_user_turn(to_connection_request_id(request_id), params) + .await; } ClientRequest::InterruptConversation { request_id, params } => { - self.interrupt_conversation(request_id, params).await; + self.interrupt_conversation(to_connection_request_id(request_id), params) + .await; } ClientRequest::AddConversationListener { request_id, params } => { - self.add_conversation_listener(request_id, params).await; + self.add_conversation_listener(to_connection_request_id(request_id), params) + .await; } ClientRequest::RemoveConversationListener { request_id, params } => { - self.remove_thread_listener(request_id, params).await; + self.remove_thread_listener(to_connection_request_id(request_id), params) + .await; } ClientRequest::GitDiffToRemote { request_id, params } => { - self.git_diff_to_origin(request_id, params.cwd).await; + self.git_diff_to_origin(to_connection_request_id(request_id), params.cwd) + .await; } ClientRequest::LoginApiKey { request_id, params } => { - self.login_api_key_v1(request_id, params).await; + self.login_api_key_v1(to_connection_request_id(request_id), params) + .await; } ClientRequest::LoginChatGpt { request_id, params: _, } => { - self.login_chatgpt_v1(request_id).await; + self.login_chatgpt_v1(to_connection_request_id(request_id)) + .await; } ClientRequest::CancelLoginChatGpt { request_id, params } => { - self.cancel_login_chatgpt(request_id, params.login_id).await; + self.cancel_login_chatgpt(to_connection_request_id(request_id), params.login_id) + .await; } ClientRequest::LogoutChatGpt { request_id, params: _, } => { - self.logout_v1(request_id).await; + self.logout_v1(to_connection_request_id(request_id)).await; } ClientRequest::GetAuthStatus { request_id, params } => { - self.get_auth_status(request_id, params).await; + self.get_auth_status(to_connection_request_id(request_id), params) + .await; } ClientRequest::GetUserSavedConfig { request_id, params: _, } => { - self.get_user_saved_config(request_id).await; + self.get_user_saved_config(to_connection_request_id(request_id)) + .await; } ClientRequest::SetDefaultModel { request_id, params } => { - self.set_default_model(request_id, params).await; + self.set_default_model(to_connection_request_id(request_id), params) + .await; } ClientRequest::GetUserAgent { request_id, params: _, } => { - self.get_user_agent(request_id).await; + self.get_user_agent(to_connection_request_id(request_id)) + .await; } ClientRequest::UserInfo { request_id, params: _, } => { - self.get_user_info(request_id).await; + self.get_user_info(to_connection_request_id(request_id)) + .await; } ClientRequest::FuzzyFileSearch { request_id, params } => { - self.fuzzy_file_search(request_id, params).await; + self.fuzzy_file_search(to_connection_request_id(request_id), params) + .await; } ClientRequest::OneOffCommandExec { request_id, params } => { - self.exec_one_off_command(request_id, params).await; + self.exec_one_off_command(to_connection_request_id(request_id), params) + .await; } ClientRequest::ExecOneOffCommand { request_id, params } => { - self.exec_one_off_command(request_id, params.into()).await; + self.exec_one_off_command(to_connection_request_id(request_id), params.into()) + .await; } ClientRequest::ConfigRead { .. } | ClientRequest::ConfigValueWrite { .. } @@ -664,15 +722,17 @@ impl CodexMessageProcessor { request_id, params: _, } => { - self.get_account_rate_limits(request_id).await; + self.get_account_rate_limits(to_connection_request_id(request_id)) + .await; } ClientRequest::FeedbackUpload { request_id, params } => { - self.upload_feedback(request_id, params).await; + self.upload_feedback(to_connection_request_id(request_id), params) + .await; } } } - async fn login_v2(&mut self, request_id: RequestId, params: LoginAccountParams) { + async fn login_v2(&mut self, request_id: ConnectionRequestId, params: LoginAccountParams) { match params { LoginAccountParams::ApiKey { api_key } => { self.login_api_key_v2(request_id, LoginApiKeyParams { api_key }) @@ -744,7 +804,11 @@ impl CodexMessageProcessor { } } - async fn login_api_key_v1(&mut self, request_id: RequestId, params: LoginApiKeyParams) { + async fn login_api_key_v1( + &mut self, + request_id: ConnectionRequestId, + params: LoginApiKeyParams, + ) { match self.login_api_key_common(¶ms).await { Ok(()) => { self.outgoing @@ -768,7 +832,11 @@ impl CodexMessageProcessor { } } - async fn login_api_key_v2(&mut self, request_id: RequestId, params: LoginApiKeyParams) { + async fn login_api_key_v2( + &mut self, + request_id: ConnectionRequestId, + params: LoginApiKeyParams, + ) { match self.login_api_key_common(¶ms).await { Ok(()) => { let response = codex_app_server_protocol::LoginAccountResponse::ApiKey {}; @@ -832,7 +900,7 @@ impl CodexMessageProcessor { } // Deprecated in favor of login_chatgpt_v2. - async fn login_chatgpt_v1(&mut self, request_id: RequestId) { + async fn login_chatgpt_v1(&mut self, request_id: ConnectionRequestId) { match self.login_chatgpt_common().await { Ok(opts) => match run_login_server(opts) { Ok(server) => { @@ -938,7 +1006,7 @@ impl CodexMessageProcessor { } } - async fn login_chatgpt_v2(&mut self, request_id: RequestId) { + async fn login_chatgpt_v2(&mut self, request_id: ConnectionRequestId) { match self.login_chatgpt_common().await { Ok(opts) => match run_login_server(opts) { Ok(server) => { @@ -1062,7 +1130,7 @@ impl CodexMessageProcessor { } } - async fn cancel_login_chatgpt(&mut self, request_id: RequestId, login_id: Uuid) { + async fn cancel_login_chatgpt(&mut self, request_id: ConnectionRequestId, login_id: Uuid) { match self.cancel_login_chatgpt_common(login_id).await { Ok(()) => { self.outgoing @@ -1080,7 +1148,11 @@ impl CodexMessageProcessor { } } - async fn cancel_login_v2(&mut self, request_id: RequestId, params: CancelLoginAccountParams) { + async fn cancel_login_v2( + &mut self, + request_id: ConnectionRequestId, + params: CancelLoginAccountParams, + ) { let login_id = params.login_id; match Uuid::parse_str(&login_id) { Ok(uuid) => { @@ -1104,7 +1176,7 @@ impl CodexMessageProcessor { async fn login_chatgpt_auth_tokens( &mut self, - request_id: RequestId, + request_id: ConnectionRequestId, id_token: String, access_token: String, ) { @@ -1229,7 +1301,7 @@ impl CodexMessageProcessor { .map(CodexAuth::api_auth_mode)) } - async fn logout_v1(&mut self, request_id: RequestId) { + async fn logout_v1(&mut self, request_id: ConnectionRequestId) { match self.logout_common().await { Ok(current_auth_method) => { self.outgoing @@ -1249,7 +1321,7 @@ impl CodexMessageProcessor { } } - async fn logout_v2(&mut self, request_id: RequestId) { + async fn logout_v2(&mut self, request_id: ConnectionRequestId) { match self.logout_common().await { Ok(current_auth_method) => { self.outgoing @@ -1278,7 +1350,7 @@ impl CodexMessageProcessor { } } - async fn get_auth_status(&self, request_id: RequestId, params: GetAuthStatusParams) { + async fn get_auth_status(&self, request_id: ConnectionRequestId, params: GetAuthStatusParams) { let include_token = params.include_token.unwrap_or(false); let do_refresh = params.refresh_token.unwrap_or(false); @@ -1327,7 +1399,7 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn get_account(&self, request_id: RequestId, params: GetAccountParams) { + async fn get_account(&self, request_id: ConnectionRequestId, params: GetAccountParams) { let do_refresh = params.refresh_token; self.refresh_token_if_requested(do_refresh).await; @@ -1379,13 +1451,13 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn get_user_agent(&self, request_id: RequestId) { + async fn get_user_agent(&self, request_id: ConnectionRequestId) { let user_agent = get_codex_user_agent(); let response = GetUserAgentResponse { user_agent }; self.outgoing.send_response(request_id, response).await; } - async fn get_account_rate_limits(&self, request_id: RequestId) { + async fn get_account_rate_limits(&self, request_id: ConnectionRequestId) { match self.fetch_account_rate_limits().await { Ok(rate_limits) => { let response = GetAccountRateLimitsResponse { @@ -1433,7 +1505,7 @@ impl CodexMessageProcessor { }) } - async fn get_user_saved_config(&self, request_id: RequestId) { + async fn get_user_saved_config(&self, request_id: ConnectionRequestId) { let service = ConfigService::new_with_defaults(self.config.codex_home.clone()); let user_saved_config: UserSavedConfig = match service.load_user_saved_config().await { Ok(config) => config, @@ -1454,7 +1526,7 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn get_user_info(&self, request_id: RequestId) { + async fn get_user_info(&self, request_id: ConnectionRequestId) { // Read alleged user email from cached auth (best-effort; not verified). let alleged_user_email = self .auth_manager @@ -1465,7 +1537,11 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn set_default_model(&self, request_id: RequestId, params: SetDefaultModelParams) { + async fn set_default_model( + &self, + request_id: ConnectionRequestId, + params: SetDefaultModelParams, + ) { let SetDefaultModelParams { model, reasoning_effort, @@ -1492,16 +1568,22 @@ impl CodexMessageProcessor { } } - async fn exec_one_off_command(&self, request_id: RequestId, params: CommandExecParams) { + async fn exec_one_off_command( + &self, + request_id: ConnectionRequestId, + params: CommandExecParams, + ) { tracing::debug!("ExecOneOffCommand params: {params:?}"); + let request = request_id.clone(); + if params.command.is_empty() { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: "command must not be empty".to_string(), data: None, }; - self.outgoing.send_error(request_id, error).await; + self.outgoing.send_error(request, error).await; return; } @@ -1532,7 +1614,7 @@ impl CodexMessageProcessor { message: format!("invalid sandbox policy: {err}"), data: None, }; - self.outgoing.send_error(request_id, error).await; + self.outgoing.send_error(request, error).await; return; } }, @@ -1541,7 +1623,7 @@ impl CodexMessageProcessor { let codex_linux_sandbox_exe = self.config.codex_linux_sandbox_exe.clone(); let outgoing = self.outgoing.clone(); - let req_id = request_id; + let request_for_task = request; let sandbox_cwd = self.config.cwd.clone(); let use_linux_sandbox_bwrap = self.config.features.enabled(Feature::UseLinuxSandboxBwrap); @@ -1562,7 +1644,7 @@ impl CodexMessageProcessor { stdout: output.stdout.text, stderr: output.stderr.text, }; - outgoing.send_response(req_id, response).await; + outgoing.send_response(request_for_task, response).await; } Err(err) => { let error = JSONRPCErrorError { @@ -1570,7 +1652,7 @@ impl CodexMessageProcessor { message: format!("exec failed: {err}"), data: None, }; - outgoing.send_error(req_id, error).await; + outgoing.send_error(request_for_task, error).await; } } }); @@ -1578,7 +1660,7 @@ impl CodexMessageProcessor { async fn process_new_conversation( &mut self, - request_id: RequestId, + request_id: ConnectionRequestId, params: NewConversationParams, ) { let NewConversationParams { @@ -1679,7 +1761,7 @@ impl CodexMessageProcessor { } } - async fn thread_start(&mut self, request_id: RequestId, params: ThreadStartParams) { + async fn thread_start(&mut self, request_id: ConnectionRequestId, params: ThreadStartParams) { let ThreadStartParams { model, model_provider, @@ -1864,7 +1946,11 @@ impl CodexMessageProcessor { } } - async fn thread_archive(&mut self, request_id: RequestId, params: ThreadArchiveParams) { + async fn thread_archive( + &mut self, + request_id: ConnectionRequestId, + params: ThreadArchiveParams, + ) { // TODO(jif) mostly rewrite this using sqlite after phase 1 let thread_id = match ThreadId::from_string(¶ms.thread_id) { Ok(id) => id, @@ -1914,7 +2000,7 @@ impl CodexMessageProcessor { } } - async fn thread_set_name(&self, request_id: RequestId, params: ThreadSetNameParams) { + async fn thread_set_name(&self, request_id: ConnectionRequestId, params: ThreadSetNameParams) { let ThreadSetNameParams { thread_id, name } = params; let Some(name) = codex_core::util::normalize_thread_name(&name) else { self.send_invalid_request_error( @@ -1944,7 +2030,11 @@ impl CodexMessageProcessor { .await; } - async fn thread_unarchive(&mut self, request_id: RequestId, params: ThreadUnarchiveParams) { + async fn thread_unarchive( + &mut self, + request_id: ConnectionRequestId, + params: ThreadUnarchiveParams, + ) { // TODO(jif) mostly rewrite this using sqlite after phase 1 let thread_id = match ThreadId::from_string(¶ms.thread_id) { Ok(id) => id, @@ -2121,7 +2211,11 @@ impl CodexMessageProcessor { } } - async fn thread_rollback(&mut self, request_id: RequestId, params: ThreadRollbackParams) { + async fn thread_rollback( + &mut self, + request_id: ConnectionRequestId, + params: ThreadRollbackParams, + ) { let ThreadRollbackParams { thread_id, num_turns, @@ -2141,18 +2235,20 @@ impl CodexMessageProcessor { } }; + let request = request_id.clone(); + { let mut map = self.pending_rollbacks.lock().await; if map.contains_key(&thread_id) { self.send_invalid_request_error( - request_id, + request.clone(), "rollback already in progress for this thread".to_string(), ) .await; return; } - map.insert(thread_id, request_id.clone()); + map.insert(thread_id, request.clone()); } if let Err(err) = thread.submit(Op::ThreadRollback { num_turns }).await { @@ -2161,12 +2257,16 @@ impl CodexMessageProcessor { let mut map = self.pending_rollbacks.lock().await; map.remove(&thread_id); - self.send_internal_error(request_id, format!("failed to start rollback: {err}")) + self.send_internal_error(request, format!("failed to start rollback: {err}")) .await; } } - async fn thread_compact_start(&self, request_id: RequestId, params: ThreadCompactStartParams) { + async fn thread_compact_start( + &self, + request_id: ConnectionRequestId, + params: ThreadCompactStartParams, + ) { let ThreadCompactStartParams { thread_id } = params; let (_, thread) = match self.load_thread(&thread_id).await { @@ -2190,7 +2290,7 @@ impl CodexMessageProcessor { } } - async fn thread_list(&self, request_id: RequestId, params: ThreadListParams) { + async fn thread_list(&self, request_id: ConnectionRequestId, params: ThreadListParams) { let ThreadListParams { cursor, limit, @@ -2231,7 +2331,11 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn thread_loaded_list(&self, request_id: RequestId, params: ThreadLoadedListParams) { + async fn thread_loaded_list( + &self, + request_id: ConnectionRequestId, + params: ThreadLoadedListParams, + ) { let ThreadLoadedListParams { cursor, limit } = params; let mut data = self .thread_manager @@ -2286,7 +2390,7 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn thread_read(&mut self, request_id: RequestId, params: ThreadReadParams) { + async fn thread_read(&mut self, request_id: ConnectionRequestId, params: ThreadReadParams) { let ThreadReadParams { thread_id, include_turns, @@ -2423,7 +2527,7 @@ impl CodexMessageProcessor { } } - async fn thread_resume(&mut self, request_id: RequestId, params: ThreadResumeParams) { + async fn thread_resume(&mut self, request_id: ConnectionRequestId, params: ThreadResumeParams) { let ThreadResumeParams { thread_id, history, @@ -2631,7 +2735,7 @@ impl CodexMessageProcessor { } } - async fn thread_fork(&mut self, request_id: RequestId, params: ThreadForkParams) { + async fn thread_fork(&mut self, request_id: ConnectionRequestId, params: ThreadForkParams) { let ThreadForkParams { thread_id, path, @@ -2836,7 +2940,7 @@ impl CodexMessageProcessor { async fn get_thread_summary( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: GetConversationSummaryParams, ) { if let GetConversationSummaryParams::ThreadId { conversation_id } = ¶ms @@ -2902,7 +3006,7 @@ impl CodexMessageProcessor { async fn handle_list_conversations( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ListConversationsParams, ) { let ListConversationsParams { @@ -3070,7 +3174,7 @@ impl CodexMessageProcessor { outgoing: Arc, thread_manager: Arc, config: Arc, - request_id: RequestId, + request_id: ConnectionRequestId, params: ModelListParams, ) { let ModelListParams { limit, cursor } = params; @@ -3133,7 +3237,7 @@ impl CodexMessageProcessor { async fn list_collaboration_modes( outgoing: Arc, thread_manager: Arc, - request_id: RequestId, + request_id: ConnectionRequestId, params: CollaborationModeListParams, ) { let CollaborationModeListParams {} = params; @@ -3144,7 +3248,7 @@ impl CodexMessageProcessor { async fn experimental_feature_list( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ExperimentalFeatureListParams, ) { let ExperimentalFeatureListParams { cursor, limit } = params; @@ -3237,7 +3341,7 @@ impl CodexMessageProcessor { async fn mock_experimental_method( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: MockExperimentalMethodParams, ) { let MockExperimentalMethodParams { value } = params; @@ -3245,7 +3349,7 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn mcp_server_refresh(&self, request_id: RequestId, _params: Option<()>) { + async fn mcp_server_refresh(&self, request_id: ConnectionRequestId, _params: Option<()>) { let config = match self.load_latest_config().await { Ok(config) => config, Err(error) => { @@ -3298,7 +3402,7 @@ impl CodexMessageProcessor { async fn mcp_server_oauth_login( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: McpServerOauthLoginParams, ) { let config = match self.load_latest_config().await { @@ -3395,26 +3499,28 @@ impl CodexMessageProcessor { async fn list_mcp_server_status( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ListMcpServerStatusParams, ) { + let request = request_id.clone(); + let outgoing = Arc::clone(&self.outgoing); let config = match self.load_latest_config().await { Ok(config) => config, Err(error) => { - self.outgoing.send_error(request_id, error).await; + self.outgoing.send_error(request, error).await; return; } }; tokio::spawn(async move { - Self::list_mcp_server_status_task(outgoing, request_id, params, config).await; + Self::list_mcp_server_status_task(outgoing, request, params, config).await; }); } async fn list_mcp_server_status_task( outgoing: Arc, - request_id: RequestId, + request_id: ConnectionRequestId, params: ListMcpServerStatusParams, config: Config, ) { @@ -3497,7 +3603,7 @@ impl CodexMessageProcessor { async fn handle_resume_conversation( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ResumeConversationParams, ) { let ResumeConversationParams { @@ -3705,7 +3811,7 @@ impl CodexMessageProcessor { async fn handle_fork_conversation( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ForkConversationParams, ) { let ForkConversationParams { @@ -3901,7 +4007,7 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn send_invalid_request_error(&self, request_id: RequestId, message: String) { + async fn send_invalid_request_error(&self, request_id: ConnectionRequestId, message: String) { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message, @@ -3910,7 +4016,7 @@ impl CodexMessageProcessor { self.outgoing.send_error(request_id, error).await; } - async fn send_internal_error(&self, request_id: RequestId, message: String) { + async fn send_internal_error(&self, request_id: ConnectionRequestId, message: String) { let error = JSONRPCErrorError { code: INTERNAL_ERROR_CODE, message, @@ -3921,7 +4027,7 @@ impl CodexMessageProcessor { async fn archive_conversation( &mut self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ArchiveConversationParams, ) { let ArchiveConversationParams { @@ -4070,7 +4176,11 @@ impl CodexMessageProcessor { }) } - async fn send_user_message(&self, request_id: RequestId, params: SendUserMessageParams) { + async fn send_user_message( + &self, + request_id: ConnectionRequestId, + params: SendUserMessageParams, + ) { let SendUserMessageParams { conversation_id, items, @@ -4114,7 +4224,7 @@ impl CodexMessageProcessor { .await; } - async fn send_user_turn(&self, request_id: RequestId, params: SendUserTurnParams) { + async fn send_user_turn(&self, request_id: ConnectionRequestId, params: SendUserTurnParams) { let SendUserTurnParams { conversation_id, items, @@ -4172,7 +4282,7 @@ impl CodexMessageProcessor { .await; } - async fn apps_list(&self, request_id: RequestId, params: AppsListParams) { + async fn apps_list(&self, request_id: ConnectionRequestId, params: AppsListParams) { let AppsListParams { cursor, limit } = params; let config = match self.load_latest_config().await { Ok(config) => config, @@ -4257,7 +4367,7 @@ impl CodexMessageProcessor { .await; } - async fn skills_list(&self, request_id: RequestId, params: SkillsListParams) { + async fn skills_list(&self, request_id: ConnectionRequestId, params: SkillsListParams) { let SkillsListParams { cwds, force_reload } = params; let cwds = if cwds.is_empty() { vec![self.config.cwd.clone()] @@ -4282,7 +4392,11 @@ impl CodexMessageProcessor { .await; } - async fn skills_remote_read(&self, request_id: RequestId, _params: SkillsRemoteReadParams) { + async fn skills_remote_read( + &self, + request_id: ConnectionRequestId, + _params: SkillsRemoteReadParams, + ) { match list_remote_skills(&self.config).await { Ok(skills) => { let data = skills @@ -4307,7 +4421,11 @@ impl CodexMessageProcessor { } } - async fn skills_remote_write(&self, request_id: RequestId, params: SkillsRemoteWriteParams) { + async fn skills_remote_write( + &self, + request_id: ConnectionRequestId, + params: SkillsRemoteWriteParams, + ) { let SkillsRemoteWriteParams { hazelnut_id, is_preload, @@ -4337,7 +4455,11 @@ impl CodexMessageProcessor { } } - async fn skills_config_write(&self, request_id: RequestId, params: SkillsConfigWriteParams) { + async fn skills_config_write( + &self, + request_id: ConnectionRequestId, + params: SkillsConfigWriteParams, + ) { let SkillsConfigWriteParams { path, enabled } = params; let edits = vec![ConfigEdit::SetSkillConfig { path, enabled }]; let result = ConfigEditsBuilder::new(&self.config.codex_home) @@ -4370,7 +4492,7 @@ impl CodexMessageProcessor { async fn interrupt_conversation( &mut self, - request_id: RequestId, + request_id: ConnectionRequestId, params: InterruptConversationParams, ) { let InterruptConversationParams { conversation_id } = params; @@ -4384,19 +4506,21 @@ impl CodexMessageProcessor { return; }; + let request = request_id.clone(); + // Record the pending interrupt so we can reply when TurnAborted arrives. { let mut map = self.pending_interrupts.lock().await; map.entry(conversation_id) .or_default() - .push((request_id, ApiVersion::V1)); + .push((request, ApiVersion::V1)); } // Submit the interrupt; we'll respond upon TurnAborted. let _ = conversation.submit(Op::Interrupt).await; } - async fn turn_start(&self, request_id: RequestId, params: TurnStartParams) { + async fn turn_start(&self, request_id: ConnectionRequestId, params: TurnStartParams) { let (_, thread) = match self.load_thread(¶ms.thread_id).await { Ok(v) => v, Err(error) => { @@ -4502,7 +4626,7 @@ impl CodexMessageProcessor { async fn emit_review_started( &self, - request_id: &RequestId, + request_id: &ConnectionRequestId, turn: Turn, parent_thread_id: String, review_thread_id: String, @@ -4526,7 +4650,7 @@ impl CodexMessageProcessor { async fn start_inline_review( &self, - request_id: &RequestId, + request_id: &ConnectionRequestId, parent_thread: Arc, review_request: ReviewRequest, display_text: &str, @@ -4556,7 +4680,7 @@ impl CodexMessageProcessor { async fn start_detached_review( &mut self, - request_id: &RequestId, + request_id: &ConnectionRequestId, parent_thread_id: ThreadId, review_request: ReviewRequest, display_text: &str, @@ -4648,7 +4772,7 @@ impl CodexMessageProcessor { Ok(()) } - async fn review_start(&mut self, request_id: RequestId, params: ReviewStartParams) { + async fn review_start(&mut self, request_id: ConnectionRequestId, params: ReviewStartParams) { let ReviewStartParams { thread_id, target, @@ -4702,7 +4826,11 @@ impl CodexMessageProcessor { } } - async fn turn_interrupt(&mut self, request_id: RequestId, params: TurnInterruptParams) { + async fn turn_interrupt( + &mut self, + request_id: ConnectionRequestId, + params: TurnInterruptParams, + ) { let TurnInterruptParams { thread_id, .. } = params; let (thread_uuid, thread) = match self.load_thread(&thread_id).await { @@ -4713,12 +4841,14 @@ impl CodexMessageProcessor { } }; + let request = request_id.clone(); + // Record the pending interrupt so we can reply when TurnAborted arrives. { let mut map = self.pending_interrupts.lock().await; map.entry(thread_uuid) .or_default() - .push((request_id, ApiVersion::V2)); + .push((request, ApiVersion::V2)); } // Submit the interrupt; we'll respond upon TurnAborted. @@ -4727,7 +4857,7 @@ impl CodexMessageProcessor { async fn add_conversation_listener( &mut self, - request_id: RequestId, + request_id: ConnectionRequestId, params: AddConversationListenerParams, ) { let AddConversationListenerParams { @@ -4750,7 +4880,7 @@ impl CodexMessageProcessor { async fn remove_thread_listener( &mut self, - request_id: RequestId, + request_id: ConnectionRequestId, params: RemoveConversationListenerParams, ) { let RemoveConversationListenerParams { subscription_id } = params; @@ -4880,7 +5010,7 @@ impl CodexMessageProcessor { Ok(subscription_id) } - async fn git_diff_to_origin(&self, request_id: RequestId, cwd: PathBuf) { + async fn git_diff_to_origin(&self, request_id: ConnectionRequestId, cwd: PathBuf) { let diff = git_diff_to_remote(&cwd).await; match diff { Some(value) => { @@ -4901,7 +5031,11 @@ impl CodexMessageProcessor { } } - async fn fuzzy_file_search(&mut self, request_id: RequestId, params: FuzzyFileSearchParams) { + async fn fuzzy_file_search( + &mut self, + request_id: ConnectionRequestId, + params: FuzzyFileSearchParams, + ) { let FuzzyFileSearchParams { query, roots, @@ -4941,7 +5075,7 @@ impl CodexMessageProcessor { self.outgoing.send_response(request_id, response).await; } - async fn upload_feedback(&self, request_id: RequestId, params: FeedbackUploadParams) { + async fn upload_feedback(&self, request_id: ConnectionRequestId, params: FeedbackUploadParams) { if !self.config.feedback_enabled { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index f7d108f55f..c558233413 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -8,14 +8,24 @@ use codex_core::config::ConfigBuilder; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; +use std::collections::HashMap; use std::io::ErrorKind; use std::io::Result as IoResult; use std::path::PathBuf; +use std::sync::Arc; use crate::message_processor::MessageProcessor; use crate::message_processor::MessageProcessorArgs; -use crate::outgoing_message::OutgoingMessage; +use crate::outgoing_message::ConnectionId; +use crate::outgoing_message::OutgoingEnvelope; use crate::outgoing_message::OutgoingMessageSender; +use crate::transport::CHANNEL_CAPACITY; +use crate::transport::ConnectionState; +use crate::transport::TransportEvent; +use crate::transport::has_initialized_connections; +use crate::transport::route_outgoing_envelope; +use crate::transport::start_stdio_connection; +use crate::transport::start_websocket_acceptor; use codex_app_server_protocol::ConfigLayerSource; use codex_app_server_protocol::ConfigWarningNotification; use codex_app_server_protocol::JSONRPCMessage; @@ -26,13 +36,9 @@ use codex_core::check_execpolicy_for_warnings; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::TextRange as CoreTextRange; use codex_feedback::CodexFeedback; -use tokio::io::AsyncBufReadExt; -use tokio::io::AsyncWriteExt; -use tokio::io::BufReader; -use tokio::io::{self}; use tokio::sync::mpsc; +use tokio::task::JoinHandle; use toml::Value as TomlValue; -use tracing::debug; use tracing::error; use tracing::info; use tracing::warn; @@ -51,11 +57,9 @@ mod fuzzy_file_search; mod message_processor; mod models; mod outgoing_message; +mod transport; -/// Size of the bounded channels used to communicate between tasks. The value -/// is a balance between throughput and memory usage – 128 messages should be -/// plenty for an interactive CLI. -const CHANNEL_CAPACITY: usize = 128; +pub use crate::transport::AppServerTransport; fn config_warning_from_error( summary: impl Into, @@ -173,32 +177,39 @@ pub async fn run_main( loader_overrides: LoaderOverrides, default_analytics_enabled: bool, ) -> IoResult<()> { - // Set up channels. - let (incoming_tx, mut incoming_rx) = mpsc::channel::(CHANNEL_CAPACITY); - let (outgoing_tx, mut outgoing_rx) = mpsc::channel::(CHANNEL_CAPACITY); + run_main_with_transport( + codex_linux_sandbox_exe, + cli_config_overrides, + loader_overrides, + default_analytics_enabled, + AppServerTransport::Stdio, + ) + .await +} - // Task: read from stdin, push to `incoming_tx`. - let stdin_reader_handle = tokio::spawn({ - async move { - let stdin = io::stdin(); - let reader = BufReader::new(stdin); - let mut lines = reader.lines(); +pub async fn run_main_with_transport( + codex_linux_sandbox_exe: Option, + cli_config_overrides: CliConfigOverrides, + loader_overrides: LoaderOverrides, + default_analytics_enabled: bool, + transport: AppServerTransport, +) -> IoResult<()> { + let (transport_event_tx, mut transport_event_rx) = + mpsc::channel::(CHANNEL_CAPACITY); + let (outgoing_tx, mut outgoing_rx) = mpsc::channel::(CHANNEL_CAPACITY); - while let Some(line) = lines.next_line().await.unwrap_or_default() { - match serde_json::from_str::(&line) { - Ok(msg) => { - if incoming_tx.send(msg).await.is_err() { - // Receiver gone – nothing left to do. - break; - } - } - Err(e) => error!("Failed to deserialize JSONRPCMessage: {e}"), - } - } - - debug!("stdin reader finished (EOF)"); + let mut stdio_handles = Vec::>::new(); + let mut websocket_accept_handle = None; + match transport { + AppServerTransport::Stdio => { + start_stdio_connection(transport_event_tx.clone(), &mut stdio_handles).await?; } - }); + AppServerTransport::WebSocket { bind_address } => { + websocket_accept_handle = + Some(start_websocket_acceptor(bind_address, transport_event_tx.clone()).await?); + } + } + let shutdown_when_no_connections = matches!(transport, AppServerTransport::Stdio); // Parse CLI overrides once and derive the base Config eagerly so later // components do not need to work with raw TOML values. @@ -327,15 +338,14 @@ pub async fn run_main( } } - // Task: process incoming messages. let processor_handle = tokio::spawn({ - let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx); + let outgoing_message_sender = Arc::new(OutgoingMessageSender::new(outgoing_tx)); let cli_overrides: Vec<(String, TomlValue)> = cli_kv_overrides.clone(); let loader_overrides = loader_overrides_for_config_api; let mut processor = MessageProcessor::new(MessageProcessorArgs { outgoing: outgoing_message_sender, codex_linux_sandbox_exe, - config: std::sync::Arc::new(config), + config: Arc::new(config), cli_overrides, loader_overrides, cloud_requirements: cloud_requirements.clone(), @@ -343,25 +353,65 @@ pub async fn run_main( config_warnings, }); let mut thread_created_rx = processor.thread_created_receiver(); + let mut connections = HashMap::::new(); async move { let mut listen_for_threads = true; loop { tokio::select! { - msg = incoming_rx.recv() => { - let Some(msg) = msg else { + event = transport_event_rx.recv() => { + let Some(event) = event else { break; }; - match msg { - JSONRPCMessage::Request(r) => processor.process_request(r).await, - JSONRPCMessage::Response(r) => processor.process_response(r).await, - JSONRPCMessage::Notification(n) => processor.process_notification(n).await, - JSONRPCMessage::Error(e) => processor.process_error(e).await, + match event { + TransportEvent::ConnectionOpened { connection_id, writer } => { + connections.insert(connection_id, ConnectionState::new(writer)); + } + TransportEvent::ConnectionClosed { connection_id } => { + connections.remove(&connection_id); + if shutdown_when_no_connections && connections.is_empty() { + break; + } + } + TransportEvent::IncomingMessage { connection_id, message } => { + match message { + JSONRPCMessage::Request(request) => { + let Some(connection_state) = connections.get_mut(&connection_id) else { + warn!("dropping request from unknown connection: {:?}", connection_id); + continue; + }; + processor + .process_request( + connection_id, + request, + &mut connection_state.session, + ) + .await; + } + JSONRPCMessage::Response(response) => { + processor.process_response(response).await; + } + JSONRPCMessage::Notification(notification) => { + processor.process_notification(notification).await; + } + JSONRPCMessage::Error(err) => { + processor.process_error(err).await; + } + } + } } } + envelope = outgoing_rx.recv() => { + let Some(envelope) = envelope else { + break; + }; + route_outgoing_envelope(&mut connections, envelope).await; + } created = thread_created_rx.recv(), if listen_for_threads => { match created { Ok(thread_id) => { - processor.try_attach_thread_listener(thread_id).await; + if has_initialized_connections(&connections) { + processor.try_attach_thread_listener(thread_id).await; + } } Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => { // TODO(jif) handle lag. @@ -382,33 +432,17 @@ pub async fn run_main( } }); - // Task: write outgoing messages to stdout. - let stdout_writer_handle = tokio::spawn(async move { - let mut stdout = io::stdout(); - while let Some(outgoing_message) = outgoing_rx.recv().await { - let Ok(value) = serde_json::to_value(outgoing_message) else { - error!("Failed to convert OutgoingMessage to JSON value"); - continue; - }; - match serde_json::to_string(&value) { - Ok(mut json) => { - json.push('\n'); - if let Err(e) = stdout.write_all(json.as_bytes()).await { - error!("Failed to write to stdout: {e}"); - break; - } - } - Err(e) => error!("Failed to serialize JSONRPCMessage: {e}"), - } - } + drop(transport_event_tx); - info!("stdout writer exited (channel closed)"); - }); + let _ = processor_handle.await; - // Wait for all tasks to finish. The typical exit path is the stdin reader - // hitting EOF which, once it drops `incoming_tx`, propagates shutdown to - // the processor and then to the stdout task. - let _ = tokio::join!(stdin_reader_handle, processor_handle, stdout_writer_handle); + if let Some(handle) = websocket_accept_handle { + handle.abort(); + } + + for handle in stdio_handles { + let _ = handle.await; + } Ok(()) } diff --git a/codex-rs/app-server/src/main.rs b/codex-rs/app-server/src/main.rs index 71d6dc338c..40dec1dc80 100644 --- a/codex-rs/app-server/src/main.rs +++ b/codex-rs/app-server/src/main.rs @@ -1,4 +1,6 @@ -use codex_app_server::run_main; +use clap::Parser; +use codex_app_server::AppServerTransport; +use codex_app_server::run_main_with_transport; use codex_arg0::arg0_dispatch_or_else; use codex_common::CliConfigOverrides; use codex_core::config_loader::LoaderOverrides; @@ -8,19 +10,34 @@ use std::path::PathBuf; // managed config file without writing to /etc. const MANAGED_CONFIG_PATH_ENV_VAR: &str = "CODEX_APP_SERVER_MANAGED_CONFIG_PATH"; +#[derive(Debug, Parser)] +struct AppServerArgs { + /// Transport endpoint URL. Supported values: `stdio://` (default), + /// `ws://IP:PORT`. + #[arg( + long = "listen", + value_name = "URL", + default_value = AppServerTransport::DEFAULT_LISTEN_URL + )] + listen: AppServerTransport, +} + fn main() -> anyhow::Result<()> { arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move { + let args = AppServerArgs::parse(); let managed_config_path = managed_config_path_from_debug_env(); let loader_overrides = LoaderOverrides { managed_config_path, ..Default::default() }; + let transport = args.listen; - run_main( + run_main_with_transport( codex_linux_sandbox_exe, CliConfigOverrides::default(), loader_overrides, false, + transport, ) .await?; Ok(()) diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index db6275dcaf..7d8da6c1b7 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; use std::sync::Arc; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; use crate::codex_message_processor::CodexMessageProcessor; use crate::codex_message_processor::CodexMessageProcessorArgs; use crate::config_api::ConfigApi; use crate::error_code::INVALID_REQUEST_ERROR_CODE; +use crate::outgoing_message::ConnectionId; +use crate::outgoing_message::ConnectionRequestId; use crate::outgoing_message::OutgoingMessageSender; use async_trait::async_trait; use codex_app_server_protocol::ChatgptAuthTokensRefreshParams; @@ -25,7 +25,6 @@ use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::JSONRPCNotification; use codex_app_server_protocol::JSONRPCRequest; use codex_app_server_protocol::JSONRPCResponse; -use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequestPayload; use codex_app_server_protocol::experimental_required_message; @@ -110,13 +109,17 @@ pub(crate) struct MessageProcessor { codex_message_processor: CodexMessageProcessor, config_api: ConfigApi, config: Arc, - initialized: bool, - experimental_api_enabled: Arc, - config_warnings: Vec, + config_warnings: Arc>, +} + +#[derive(Debug, Default)] +pub(crate) struct ConnectionSessionState { + pub(crate) initialized: bool, + experimental_api_enabled: bool, } pub(crate) struct MessageProcessorArgs { - pub(crate) outgoing: OutgoingMessageSender, + pub(crate) outgoing: Arc, pub(crate) codex_linux_sandbox_exe: Option, pub(crate) config: Arc, pub(crate) cli_overrides: Vec<(String, TomlValue)>, @@ -140,8 +143,6 @@ impl MessageProcessor { feedback, config_warnings, } = args; - let outgoing = Arc::new(outgoing); - let experimental_api_enabled = Arc::new(AtomicBool::new(false)); let auth_manager = AuthManager::shared( config.codex_home.clone(), false, @@ -178,14 +179,20 @@ impl MessageProcessor { codex_message_processor, config_api, config, - initialized: false, - experimental_api_enabled, - config_warnings, + config_warnings: Arc::new(config_warnings), } } - pub(crate) async fn process_request(&mut self, request: JSONRPCRequest) { - let request_id = request.id.clone(); + pub(crate) async fn process_request( + &mut self, + connection_id: ConnectionId, + request: JSONRPCRequest, + session: &mut ConnectionSessionState, + ) { + let request_id = ConnectionRequestId { + connection_id, + request_id: request.id.clone(), + }; let request_json = match serde_json::to_value(&request) { Ok(request_json) => request_json, Err(err) => { @@ -216,7 +223,11 @@ impl MessageProcessor { // Handle Initialize internally so CodexMessageProcessor does not have to concern // itself with the `initialized` bool. ClientRequest::Initialize { request_id, params } => { - if self.initialized { + let request_id = ConnectionRequestId { + connection_id, + request_id, + }; + if session.initialized { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: "Already initialized".to_string(), @@ -225,12 +236,16 @@ impl MessageProcessor { self.outgoing.send_error(request_id, error).await; return; } else { - let experimental_api_enabled = params + // TODO(maxj): Revisit capability scoping for `experimental_api_enabled`. + // Current behavior is per-connection. Reviewer feedback notes this can + // create odd cross-client behavior (for example dynamic tool calls on a + // shared thread when another connected client did not opt into + // experimental API). Proposed direction is instance-global first-write-wins + // with initialize-time mismatch rejection. + session.experimental_api_enabled = params .capabilities .as_ref() .is_some_and(|cap| cap.experimental_api); - self.experimental_api_enabled - .store(experimental_api_enabled, Ordering::Relaxed); let ClientInfo { name, title: _title, @@ -246,7 +261,7 @@ impl MessageProcessor { ), data: None, }; - self.outgoing.send_error(request_id, error).await; + self.outgoing.send_error(request_id.clone(), error).await; return; } SetOriginatorError::AlreadyInitialized => { @@ -267,22 +282,20 @@ impl MessageProcessor { let response = InitializeResponse { user_agent }; self.outgoing.send_response(request_id, response).await; - self.initialized = true; - if !self.config_warnings.is_empty() { - for notification in self.config_warnings.drain(..) { - self.outgoing - .send_server_notification(ServerNotification::ConfigWarning( - notification, - )) - .await; - } + session.initialized = true; + for notification in self.config_warnings.iter().cloned() { + self.outgoing + .send_server_notification(ServerNotification::ConfigWarning( + notification, + )) + .await; } return; } } _ => { - if !self.initialized { + if !session.initialized { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: "Not initialized".to_string(), @@ -295,7 +308,7 @@ impl MessageProcessor { } if let Some(reason) = codex_request.experimental_reason() - && !self.experimental_api_enabled.load(Ordering::Relaxed) + && !session.experimental_api_enabled { let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, @@ -308,22 +321,49 @@ impl MessageProcessor { match codex_request { ClientRequest::ConfigRead { request_id, params } => { - self.handle_config_read(request_id, params).await; + self.handle_config_read( + ConnectionRequestId { + connection_id, + request_id, + }, + params, + ) + .await; } ClientRequest::ConfigValueWrite { request_id, params } => { - self.handle_config_value_write(request_id, params).await; + self.handle_config_value_write( + ConnectionRequestId { + connection_id, + request_id, + }, + params, + ) + .await; } ClientRequest::ConfigBatchWrite { request_id, params } => { - self.handle_config_batch_write(request_id, params).await; + self.handle_config_batch_write( + ConnectionRequestId { + connection_id, + request_id, + }, + params, + ) + .await; } ClientRequest::ConfigRequirementsRead { request_id, params: _, } => { - self.handle_config_requirements_read(request_id).await; + self.handle_config_requirements_read(ConnectionRequestId { + connection_id, + request_id, + }) + .await; } other => { - self.codex_message_processor.process_request(other).await; + self.codex_message_processor + .process_request(connection_id, other) + .await; } } } @@ -339,9 +379,6 @@ impl MessageProcessor { } pub(crate) async fn try_attach_thread_listener(&mut self, thread_id: ThreadId) { - if !self.initialized { - return; - } self.codex_message_processor .try_attach_thread_listener(thread_id) .await; @@ -360,7 +397,7 @@ impl MessageProcessor { self.outgoing.notify_client_error(err.id, err.error).await; } - async fn handle_config_read(&self, request_id: RequestId, params: ConfigReadParams) { + async fn handle_config_read(&self, request_id: ConnectionRequestId, params: ConfigReadParams) { match self.config_api.read(params).await { Ok(response) => self.outgoing.send_response(request_id, response).await, Err(error) => self.outgoing.send_error(request_id, error).await, @@ -369,7 +406,7 @@ impl MessageProcessor { async fn handle_config_value_write( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ConfigValueWriteParams, ) { match self.config_api.write_value(params).await { @@ -380,7 +417,7 @@ impl MessageProcessor { async fn handle_config_batch_write( &self, - request_id: RequestId, + request_id: ConnectionRequestId, params: ConfigBatchWriteParams, ) { match self.config_api.batch_write(params).await { @@ -389,7 +426,7 @@ impl MessageProcessor { } } - async fn handle_config_requirements_read(&self, request_id: RequestId) { + async fn handle_config_requirements_read(&self, request_id: ConnectionRequestId) { match self.config_api.config_requirements_read().await { Ok(response) => self.outgoing.send_response(request_id, response).await, Err(error) => self.outgoing.send_error(request_id, error).await, diff --git a/codex-rs/app-server/src/outgoing_message.rs b/codex-rs/app-server/src/outgoing_message.rs index be89775d86..a5219dc2dc 100644 --- a/codex-rs/app-server/src/outgoing_message.rs +++ b/codex-rs/app-server/src/outgoing_message.rs @@ -19,17 +19,39 @@ use crate::error_code::INTERNAL_ERROR_CODE; #[cfg(test)] use codex_protocol::account::PlanType; +/// Stable identifier for a transport connection. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub(crate) struct ConnectionId(pub(crate) u64); + +/// Stable identifier for a client request scoped to a transport connection. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) struct ConnectionRequestId { + pub(crate) connection_id: ConnectionId, + pub(crate) request_id: RequestId, +} + +#[derive(Debug, Clone)] +pub(crate) enum OutgoingEnvelope { + ToConnection { + connection_id: ConnectionId, + message: OutgoingMessage, + }, + Broadcast { + message: OutgoingMessage, + }, +} + /// Sends messages to the client and manages request callbacks. pub(crate) struct OutgoingMessageSender { - next_request_id: AtomicI64, - sender: mpsc::Sender, + next_server_request_id: AtomicI64, + sender: mpsc::Sender, request_id_to_callback: Mutex>>, } impl OutgoingMessageSender { - pub(crate) fn new(sender: mpsc::Sender) -> Self { + pub(crate) fn new(sender: mpsc::Sender) -> Self { Self { - next_request_id: AtomicI64::new(0), + next_server_request_id: AtomicI64::new(0), sender, request_id_to_callback: Mutex::new(HashMap::new()), } @@ -47,7 +69,7 @@ impl OutgoingMessageSender { &self, request: ServerRequestPayload, ) -> (RequestId, oneshot::Receiver) { - let id = RequestId::Integer(self.next_request_id.fetch_add(1, Ordering::Relaxed)); + let id = RequestId::Integer(self.next_server_request_id.fetch_add(1, Ordering::Relaxed)); let outgoing_message_id = id.clone(); let (tx_approve, rx_approve) = oneshot::channel(); { @@ -57,7 +79,13 @@ impl OutgoingMessageSender { let outgoing_message = OutgoingMessage::Request(request.request_with_id(outgoing_message_id.clone())); - if let Err(err) = self.sender.send(outgoing_message).await { + if let Err(err) = self + .sender + .send(OutgoingEnvelope::Broadcast { + message: outgoing_message, + }) + .await + { warn!("failed to send request {outgoing_message_id:?} to client: {err:?}"); let mut request_id_to_callback = self.request_id_to_callback.lock().await; request_id_to_callback.remove(&outgoing_message_id); @@ -107,17 +135,31 @@ impl OutgoingMessageSender { entry.is_some() } - pub(crate) async fn send_response(&self, id: RequestId, response: T) { + pub(crate) async fn send_response( + &self, + request_id: ConnectionRequestId, + response: T, + ) { match serde_json::to_value(response) { Ok(result) => { - let outgoing_message = OutgoingMessage::Response(OutgoingResponse { id, result }); - if let Err(err) = self.sender.send(outgoing_message).await { + let outgoing_message = OutgoingMessage::Response(OutgoingResponse { + id: request_id.request_id, + result, + }); + if let Err(err) = self + .sender + .send(OutgoingEnvelope::ToConnection { + connection_id: request_id.connection_id, + message: outgoing_message, + }) + .await + { warn!("failed to send response to client: {err:?}"); } } Err(err) => { self.send_error( - id, + request_id, JSONRPCErrorError { code: INTERNAL_ERROR_CODE, message: format!("failed to serialize response: {err}"), @@ -132,7 +174,9 @@ impl OutgoingMessageSender { pub(crate) async fn send_server_notification(&self, notification: ServerNotification) { if let Err(err) = self .sender - .send(OutgoingMessage::AppServerNotification(notification)) + .send(OutgoingEnvelope::Broadcast { + message: OutgoingMessage::AppServerNotification(notification), + }) .await { warn!("failed to send server notification to client: {err:?}"); @@ -143,14 +187,34 @@ impl OutgoingMessageSender { /// [`OutgoingMessage::Notification`] should be removed. pub(crate) async fn send_notification(&self, notification: OutgoingNotification) { let outgoing_message = OutgoingMessage::Notification(notification); - if let Err(err) = self.sender.send(outgoing_message).await { + if let Err(err) = self + .sender + .send(OutgoingEnvelope::Broadcast { + message: outgoing_message, + }) + .await + { warn!("failed to send notification to client: {err:?}"); } } - pub(crate) async fn send_error(&self, id: RequestId, error: JSONRPCErrorError) { - let outgoing_message = OutgoingMessage::Error(OutgoingError { id, error }); - if let Err(err) = self.sender.send(outgoing_message).await { + pub(crate) async fn send_error( + &self, + request_id: ConnectionRequestId, + error: JSONRPCErrorError, + ) { + let outgoing_message = OutgoingMessage::Error(OutgoingError { + id: request_id.request_id, + error, + }); + if let Err(err) = self + .sender + .send(OutgoingEnvelope::ToConnection { + connection_id: request_id.connection_id, + message: outgoing_message, + }) + .await + { warn!("failed to send error to client: {err:?}"); } } @@ -190,6 +254,8 @@ pub(crate) struct OutgoingError { #[cfg(test)] mod tests { + use std::time::Duration; + use codex_app_server_protocol::AccountLoginCompletedNotification; use codex_app_server_protocol::AccountRateLimitsUpdatedNotification; use codex_app_server_protocol::AccountUpdatedNotification; @@ -200,6 +266,7 @@ mod tests { use codex_app_server_protocol::RateLimitWindow; use pretty_assertions::assert_eq; use serde_json::json; + use tokio::time::timeout; use uuid::Uuid; use super::*; @@ -336,4 +403,75 @@ mod tests { "ensure the notification serializes correctly" ); } + + #[tokio::test] + async fn send_response_routes_to_target_connection() { + let (tx, mut rx) = mpsc::channel::(4); + let outgoing = OutgoingMessageSender::new(tx); + let request_id = ConnectionRequestId { + connection_id: ConnectionId(42), + request_id: RequestId::Integer(7), + }; + + outgoing + .send_response(request_id.clone(), json!({ "ok": true })) + .await; + + let envelope = timeout(Duration::from_secs(1), rx.recv()) + .await + .expect("should receive envelope before timeout") + .expect("channel should contain one message"); + + match envelope { + OutgoingEnvelope::ToConnection { + connection_id, + message, + } => { + assert_eq!(connection_id, ConnectionId(42)); + let OutgoingMessage::Response(response) = message else { + panic!("expected response message"); + }; + assert_eq!(response.id, request_id.request_id); + assert_eq!(response.result, json!({ "ok": true })); + } + other => panic!("expected targeted response envelope, got: {other:?}"), + } + } + + #[tokio::test] + async fn send_error_routes_to_target_connection() { + let (tx, mut rx) = mpsc::channel::(4); + let outgoing = OutgoingMessageSender::new(tx); + let request_id = ConnectionRequestId { + connection_id: ConnectionId(9), + request_id: RequestId::Integer(3), + }; + let error = JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: "boom".to_string(), + data: None, + }; + + outgoing.send_error(request_id.clone(), error.clone()).await; + + let envelope = timeout(Duration::from_secs(1), rx.recv()) + .await + .expect("should receive envelope before timeout") + .expect("channel should contain one message"); + + match envelope { + OutgoingEnvelope::ToConnection { + connection_id, + message, + } => { + assert_eq!(connection_id, ConnectionId(9)); + let OutgoingMessage::Error(outgoing_error) = message else { + panic!("expected error message"); + }; + assert_eq!(outgoing_error.id, RequestId::Integer(3)); + assert_eq!(outgoing_error.error, error); + } + other => panic!("expected targeted error envelope, got: {other:?}"), + } + } } diff --git a/codex-rs/app-server/src/transport.rs b/codex-rs/app-server/src/transport.rs new file mode 100644 index 0000000000..52f30055d0 --- /dev/null +++ b/codex-rs/app-server/src/transport.rs @@ -0,0 +1,424 @@ +use crate::message_processor::ConnectionSessionState; +use crate::outgoing_message::ConnectionId; +use crate::outgoing_message::OutgoingEnvelope; +use crate::outgoing_message::OutgoingMessage; +use codex_app_server_protocol::JSONRPCMessage; +use futures::SinkExt; +use futures::StreamExt; +use std::collections::HashMap; +use std::io::ErrorKind; +use std::io::Result as IoResult; +use std::net::SocketAddr; +use std::str::FromStr; +use std::sync::Arc; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +use tokio::io::AsyncBufReadExt; +use tokio::io::AsyncWriteExt; +use tokio::io::BufReader; +use tokio::io::{self}; +use tokio::net::TcpListener; +use tokio::net::TcpStream; +use tokio::sync::mpsc; +use tokio::task::JoinHandle; +use tokio_tungstenite::accept_async; +use tokio_tungstenite::tungstenite::Message as WebSocketMessage; +use tracing::debug; +use tracing::error; +use tracing::info; +use tracing::warn; + +/// Size of the bounded channels used to communicate between tasks. The value +/// is a balance between throughput and memory usage - 128 messages should be +/// plenty for an interactive CLI. +pub(crate) const CHANNEL_CAPACITY: usize = 128; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AppServerTransport { + Stdio, + WebSocket { bind_address: SocketAddr }, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum AppServerTransportParseError { + UnsupportedListenUrl(String), + InvalidWebSocketListenUrl(String), +} + +impl std::fmt::Display for AppServerTransportParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AppServerTransportParseError::UnsupportedListenUrl(listen_url) => write!( + f, + "unsupported --listen URL `{listen_url}`; expected `stdio://` or `ws://IP:PORT`" + ), + AppServerTransportParseError::InvalidWebSocketListenUrl(listen_url) => write!( + f, + "invalid websocket --listen URL `{listen_url}`; expected `ws://IP:PORT`" + ), + } + } +} + +impl std::error::Error for AppServerTransportParseError {} + +impl AppServerTransport { + pub const DEFAULT_LISTEN_URL: &'static str = "stdio://"; + + pub fn from_listen_url(listen_url: &str) -> Result { + if listen_url == Self::DEFAULT_LISTEN_URL { + return Ok(Self::Stdio); + } + + if let Some(socket_addr) = listen_url.strip_prefix("ws://") { + let bind_address = socket_addr.parse::().map_err(|_| { + AppServerTransportParseError::InvalidWebSocketListenUrl(listen_url.to_string()) + })?; + return Ok(Self::WebSocket { bind_address }); + } + + Err(AppServerTransportParseError::UnsupportedListenUrl( + listen_url.to_string(), + )) + } +} + +impl FromStr for AppServerTransport { + type Err = AppServerTransportParseError; + + fn from_str(s: &str) -> Result { + Self::from_listen_url(s) + } +} + +#[derive(Debug)] +pub(crate) enum TransportEvent { + ConnectionOpened { + connection_id: ConnectionId, + writer: mpsc::Sender, + }, + ConnectionClosed { + connection_id: ConnectionId, + }, + IncomingMessage { + connection_id: ConnectionId, + message: JSONRPCMessage, + }, +} + +pub(crate) struct ConnectionState { + pub(crate) writer: mpsc::Sender, + pub(crate) session: ConnectionSessionState, +} + +impl ConnectionState { + pub(crate) fn new(writer: mpsc::Sender) -> Self { + Self { + writer, + session: ConnectionSessionState::default(), + } + } +} + +pub(crate) async fn start_stdio_connection( + transport_event_tx: mpsc::Sender, + stdio_handles: &mut Vec>, +) -> IoResult<()> { + let connection_id = ConnectionId(0); + let (writer_tx, mut writer_rx) = mpsc::channel::(CHANNEL_CAPACITY); + transport_event_tx + .send(TransportEvent::ConnectionOpened { + connection_id, + writer: writer_tx, + }) + .await + .map_err(|_| std::io::Error::new(ErrorKind::BrokenPipe, "processor unavailable"))?; + + let transport_event_tx_for_reader = transport_event_tx.clone(); + stdio_handles.push(tokio::spawn(async move { + let stdin = io::stdin(); + let reader = BufReader::new(stdin); + let mut lines = reader.lines(); + + loop { + match lines.next_line().await { + Ok(Some(line)) => { + if !forward_incoming_message( + &transport_event_tx_for_reader, + connection_id, + &line, + ) + .await + { + break; + } + } + Ok(None) => break, + Err(err) => { + error!("Failed reading stdin: {err}"); + break; + } + } + } + + let _ = transport_event_tx_for_reader + .send(TransportEvent::ConnectionClosed { connection_id }) + .await; + debug!("stdin reader finished (EOF)"); + })); + + stdio_handles.push(tokio::spawn(async move { + let mut stdout = io::stdout(); + while let Some(outgoing_message) = writer_rx.recv().await { + let Some(mut json) = serialize_outgoing_message(outgoing_message) else { + continue; + }; + json.push('\n'); + if let Err(err) = stdout.write_all(json.as_bytes()).await { + error!("Failed to write to stdout: {err}"); + break; + } + } + info!("stdout writer exited (channel closed)"); + })); + + Ok(()) +} + +pub(crate) async fn start_websocket_acceptor( + bind_address: SocketAddr, + transport_event_tx: mpsc::Sender, +) -> IoResult> { + let listener = TcpListener::bind(bind_address).await?; + let local_addr = listener.local_addr()?; + info!("app-server websocket listening on ws://{local_addr}"); + + let connection_counter = Arc::new(AtomicU64::new(1)); + Ok(tokio::spawn(async move { + loop { + match listener.accept().await { + Ok((stream, _peer_addr)) => { + let connection_id = + ConnectionId(connection_counter.fetch_add(1, Ordering::Relaxed)); + let transport_event_tx_for_connection = transport_event_tx.clone(); + tokio::spawn(async move { + run_websocket_connection( + connection_id, + stream, + transport_event_tx_for_connection, + ) + .await; + }); + } + Err(err) => { + error!("failed to accept websocket connection: {err}"); + } + } + } + })) +} + +async fn run_websocket_connection( + connection_id: ConnectionId, + stream: TcpStream, + transport_event_tx: mpsc::Sender, +) { + let websocket_stream = match accept_async(stream).await { + Ok(stream) => stream, + Err(err) => { + warn!("failed to complete websocket handshake: {err}"); + return; + } + }; + + let (writer_tx, mut writer_rx) = mpsc::channel::(CHANNEL_CAPACITY); + if transport_event_tx + .send(TransportEvent::ConnectionOpened { + connection_id, + writer: writer_tx, + }) + .await + .is_err() + { + return; + } + + let (mut websocket_writer, mut websocket_reader) = websocket_stream.split(); + loop { + tokio::select! { + outgoing_message = writer_rx.recv() => { + let Some(outgoing_message) = outgoing_message else { + break; + }; + let Some(json) = serialize_outgoing_message(outgoing_message) else { + continue; + }; + if websocket_writer.send(WebSocketMessage::Text(json.into())).await.is_err() { + break; + } + } + incoming_message = websocket_reader.next() => { + match incoming_message { + Some(Ok(WebSocketMessage::Text(text))) => { + if !forward_incoming_message(&transport_event_tx, connection_id, &text).await { + break; + } + } + Some(Ok(WebSocketMessage::Ping(payload))) => { + if websocket_writer.send(WebSocketMessage::Pong(payload)).await.is_err() { + break; + } + } + Some(Ok(WebSocketMessage::Pong(_))) => {} + Some(Ok(WebSocketMessage::Close(_))) | None => break, + Some(Ok(WebSocketMessage::Binary(_))) => { + warn!("dropping unsupported binary websocket message"); + } + Some(Ok(WebSocketMessage::Frame(_))) => {} + Some(Err(err)) => { + warn!("websocket receive error: {err}"); + break; + } + } + } + } + } + + let _ = transport_event_tx + .send(TransportEvent::ConnectionClosed { connection_id }) + .await; +} + +async fn forward_incoming_message( + transport_event_tx: &mpsc::Sender, + connection_id: ConnectionId, + payload: &str, +) -> bool { + match serde_json::from_str::(payload) { + Ok(message) => transport_event_tx + .send(TransportEvent::IncomingMessage { + connection_id, + message, + }) + .await + .is_ok(), + Err(err) => { + error!("Failed to deserialize JSONRPCMessage: {err}"); + true + } + } +} + +fn serialize_outgoing_message(outgoing_message: OutgoingMessage) -> Option { + let value = match serde_json::to_value(outgoing_message) { + Ok(value) => value, + Err(err) => { + error!("Failed to convert OutgoingMessage to JSON value: {err}"); + return None; + } + }; + match serde_json::to_string(&value) { + Ok(json) => Some(json), + Err(err) => { + error!("Failed to serialize JSONRPCMessage: {err}"); + None + } + } +} + +pub(crate) async fn route_outgoing_envelope( + connections: &mut HashMap, + envelope: OutgoingEnvelope, +) { + match envelope { + OutgoingEnvelope::ToConnection { + connection_id, + message, + } => { + let Some(connection_state) = connections.get(&connection_id) else { + warn!( + "dropping message for disconnected connection: {:?}", + connection_id + ); + return; + }; + if connection_state.writer.send(message).await.is_err() { + connections.remove(&connection_id); + } + } + OutgoingEnvelope::Broadcast { message } => { + let target_connections: Vec = connections + .iter() + .filter_map(|(connection_id, connection_state)| { + if connection_state.session.initialized { + Some(*connection_id) + } else { + None + } + }) + .collect(); + + for connection_id in target_connections { + let Some(connection_state) = connections.get(&connection_id) else { + continue; + }; + if connection_state.writer.send(message.clone()).await.is_err() { + connections.remove(&connection_id); + } + } + } + } +} + +pub(crate) fn has_initialized_connections( + connections: &HashMap, +) -> bool { + connections + .values() + .any(|connection| connection.session.initialized) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn app_server_transport_parses_stdio_listen_url() { + let transport = AppServerTransport::from_listen_url(AppServerTransport::DEFAULT_LISTEN_URL) + .expect("stdio listen URL should parse"); + assert_eq!(transport, AppServerTransport::Stdio); + } + + #[test] + fn app_server_transport_parses_websocket_listen_url() { + let transport = AppServerTransport::from_listen_url("ws://127.0.0.1:1234") + .expect("websocket listen URL should parse"); + assert_eq!( + transport, + AppServerTransport::WebSocket { + bind_address: "127.0.0.1:1234".parse().expect("valid socket address"), + } + ); + } + + #[test] + fn app_server_transport_rejects_invalid_websocket_listen_url() { + let err = AppServerTransport::from_listen_url("ws://localhost:1234") + .expect_err("hostname bind address should be rejected"); + assert_eq!( + err.to_string(), + "invalid websocket --listen URL `ws://localhost:1234`; expected `ws://IP:PORT`" + ); + } + + #[test] + fn app_server_transport_rejects_unsupported_listen_url() { + let err = AppServerTransport::from_listen_url("http://127.0.0.1:1234") + .expect_err("unsupported scheme should fail"); + assert_eq!( + err.to_string(), + "unsupported --listen URL `http://127.0.0.1:1234`; expected `stdio://` or `ws://IP:PORT`" + ); + } +} diff --git a/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs new file mode 100644 index 0000000000..ddd4326fc9 --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/connection_handling_websocket.rs @@ -0,0 +1,263 @@ +use anyhow::Context; +use anyhow::Result; +use anyhow::bail; +use app_test_support::create_mock_responses_server_sequence_unchecked; +use codex_app_server_protocol::ClientInfo; +use codex_app_server_protocol::InitializeParams; +use codex_app_server_protocol::JSONRPCError; +use codex_app_server_protocol::JSONRPCMessage; +use codex_app_server_protocol::JSONRPCRequest; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::RequestId; +use futures::SinkExt; +use futures::StreamExt; +use serde_json::json; +use std::net::SocketAddr; +use std::path::Path; +use std::process::Stdio; +use tempfile::TempDir; +use tokio::io::AsyncBufReadExt; +use tokio::process::Child; +use tokio::process::Command; +use tokio::time::Duration; +use tokio::time::Instant; +use tokio::time::sleep; +use tokio::time::timeout; +use tokio_tungstenite::MaybeTlsStream; +use tokio_tungstenite::WebSocketStream; +use tokio_tungstenite::connect_async; +use tokio_tungstenite::tungstenite::Message as WebSocketMessage; + +const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(5); + +type WsClient = WebSocketStream>; + +#[tokio::test] +async fn websocket_transport_routes_per_connection_handshake_and_responses() -> Result<()> { + let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await; + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri(), "never")?; + + let bind_addr = reserve_local_addr()?; + let mut process = spawn_websocket_server(codex_home.path(), bind_addr).await?; + + let mut ws1 = connect_websocket(bind_addr).await?; + let mut ws2 = connect_websocket(bind_addr).await?; + + send_initialize_request(&mut ws1, 1, "ws_client_one").await?; + let first_init = read_response_for_id(&mut ws1, 1).await?; + assert_eq!(first_init.id, RequestId::Integer(1)); + + // Initialize responses are request-scoped and must not leak to other + // connections. + assert_no_message(&mut ws2, Duration::from_millis(250)).await?; + + send_config_read_request(&mut ws2, 2).await?; + let not_initialized = read_error_for_id(&mut ws2, 2).await?; + assert_eq!(not_initialized.error.message, "Not initialized"); + + send_initialize_request(&mut ws2, 3, "ws_client_two").await?; + let second_init = read_response_for_id(&mut ws2, 3).await?; + assert_eq!(second_init.id, RequestId::Integer(3)); + + // Same request-id on different connections must route independently. + send_config_read_request(&mut ws1, 77).await?; + send_config_read_request(&mut ws2, 77).await?; + let ws1_config = read_response_for_id(&mut ws1, 77).await?; + let ws2_config = read_response_for_id(&mut ws2, 77).await?; + + assert_eq!(ws1_config.id, RequestId::Integer(77)); + assert_eq!(ws2_config.id, RequestId::Integer(77)); + assert!(ws1_config.result.get("config").is_some()); + assert!(ws2_config.result.get("config").is_some()); + + process + .kill() + .await + .context("failed to stop websocket app-server process")?; + Ok(()) +} + +async fn spawn_websocket_server(codex_home: &Path, bind_addr: SocketAddr) -> Result { + let program = codex_utils_cargo_bin::cargo_bin("codex-app-server") + .context("should find app-server binary")?; + let mut cmd = Command::new(program); + cmd.arg("--listen") + .arg(format!("ws://{bind_addr}")) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .env("CODEX_HOME", codex_home) + .env("RUST_LOG", "debug"); + let mut process = cmd + .kill_on_drop(true) + .spawn() + .context("failed to spawn websocket app-server process")?; + + if let Some(stderr) = process.stderr.take() { + let mut stderr_reader = tokio::io::BufReader::new(stderr).lines(); + tokio::spawn(async move { + while let Ok(Some(line)) = stderr_reader.next_line().await { + eprintln!("[websocket app-server stderr] {line}"); + } + }); + } + + Ok(process) +} + +fn reserve_local_addr() -> Result { + let listener = std::net::TcpListener::bind("127.0.0.1:0")?; + let addr = listener.local_addr()?; + drop(listener); + Ok(addr) +} + +async fn connect_websocket(bind_addr: SocketAddr) -> Result { + let url = format!("ws://{bind_addr}"); + let deadline = Instant::now() + Duration::from_secs(10); + loop { + match connect_async(&url).await { + Ok((stream, _response)) => return Ok(stream), + Err(err) => { + if Instant::now() >= deadline { + bail!("failed to connect websocket to {url}: {err}"); + } + sleep(Duration::from_millis(50)).await; + } + } + } +} + +async fn send_initialize_request(stream: &mut WsClient, id: i64, client_name: &str) -> Result<()> { + let params = InitializeParams { + client_info: ClientInfo { + name: client_name.to_string(), + title: Some("WebSocket Test Client".to_string()), + version: "0.1.0".to_string(), + }, + capabilities: None, + }; + send_request( + stream, + "initialize", + id, + Some(serde_json::to_value(params)?), + ) + .await +} + +async fn send_config_read_request(stream: &mut WsClient, id: i64) -> Result<()> { + send_request( + stream, + "config/read", + id, + Some(json!({ "includeLayers": false })), + ) + .await +} + +async fn send_request( + stream: &mut WsClient, + method: &str, + id: i64, + params: Option, +) -> Result<()> { + let message = JSONRPCMessage::Request(JSONRPCRequest { + id: RequestId::Integer(id), + method: method.to_string(), + params, + }); + send_jsonrpc(stream, message).await +} + +async fn send_jsonrpc(stream: &mut WsClient, message: JSONRPCMessage) -> Result<()> { + let payload = serde_json::to_string(&message)?; + stream + .send(WebSocketMessage::Text(payload.into())) + .await + .context("failed to send websocket frame") +} + +async fn read_response_for_id(stream: &mut WsClient, id: i64) -> Result { + let target_id = RequestId::Integer(id); + loop { + let message = read_jsonrpc_message(stream).await?; + if let JSONRPCMessage::Response(response) = message + && response.id == target_id + { + return Ok(response); + } + } +} + +async fn read_error_for_id(stream: &mut WsClient, id: i64) -> Result { + let target_id = RequestId::Integer(id); + loop { + let message = read_jsonrpc_message(stream).await?; + if let JSONRPCMessage::Error(err) = message + && err.id == target_id + { + return Ok(err); + } + } +} + +async fn read_jsonrpc_message(stream: &mut WsClient) -> Result { + loop { + let frame = timeout(DEFAULT_READ_TIMEOUT, stream.next()) + .await + .context("timed out waiting for websocket frame")? + .context("websocket stream ended unexpectedly")? + .context("failed to read websocket frame")?; + + match frame { + WebSocketMessage::Text(text) => return Ok(serde_json::from_str(text.as_ref())?), + WebSocketMessage::Ping(payload) => { + stream.send(WebSocketMessage::Pong(payload)).await?; + } + WebSocketMessage::Pong(_) => {} + WebSocketMessage::Close(frame) => { + bail!("websocket closed unexpectedly: {frame:?}") + } + WebSocketMessage::Binary(_) => bail!("unexpected binary websocket frame"), + WebSocketMessage::Frame(_) => {} + } + } +} + +async fn assert_no_message(stream: &mut WsClient, wait_for: Duration) -> Result<()> { + match timeout(wait_for, stream.next()).await { + Ok(Some(Ok(frame))) => bail!("unexpected frame while waiting for silence: {frame:?}"), + Ok(Some(Err(err))) => bail!("unexpected websocket read error: {err}"), + Ok(None) => bail!("websocket closed unexpectedly while waiting for silence"), + Err(_) => Ok(()), + } +} + +fn create_config_toml( + codex_home: &Path, + server_uri: &str, + approval_policy: &str, +) -> std::io::Result<()> { + let config_toml = codex_home.join("config.toml"); + std::fs::write( + config_toml, + format!( + r#" +model = "mock-model" +approval_policy = "{approval_policy}" +sandbox_mode = "read-only" + +model_provider = "mock_provider" + +[model_providers.mock_provider] +name = "Mock provider for test" +base_url = "{server_uri}/v1" +wire_api = "responses" +request_max_retries = 0 +stream_max_retries = 0 +"# + ), + ) +} diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 2130c6e896..0894504a0f 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -4,6 +4,7 @@ mod app_list; mod collaboration_mode_list; mod compaction; mod config_rpc; +mod connection_handling_websocket; mod dynamic_tools; mod experimental_api; mod experimental_feature_list; diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index defc063eb6..1cc4fcdaa7 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -306,6 +306,15 @@ struct AppServerCommand { #[command(subcommand)] subcommand: Option, + /// Transport endpoint URL. Supported values: `stdio://` (default), + /// `ws://IP:PORT`. + #[arg( + long = "listen", + value_name = "URL", + default_value = codex_app_server::AppServerTransport::DEFAULT_LISTEN_URL + )] + listen: codex_app_server::AppServerTransport, + /// Controls whether analytics are enabled by default. /// /// Analytics are disabled by default for app-server. Users have to explicitly opt in @@ -587,11 +596,13 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() } Some(Subcommand::AppServer(app_server_cli)) => match app_server_cli.subcommand { None => { - codex_app_server::run_main( + let transport = app_server_cli.listen; + codex_app_server::run_main_with_transport( codex_linux_sandbox_exe, root_config_overrides, codex_core::config_loader::LoaderOverrides::default(), app_server_cli.analytics_default_enabled, + transport, ) .await?; } @@ -1328,6 +1339,10 @@ mod tests { fn app_server_analytics_default_disabled_without_flag() { let app_server = app_server_from_args(["codex", "app-server"].as_ref()); assert!(!app_server.analytics_default_enabled); + assert_eq!( + app_server.listen, + codex_app_server::AppServerTransport::Stdio + ); } #[test] @@ -1337,6 +1352,36 @@ mod tests { assert!(app_server.analytics_default_enabled); } + #[test] + fn app_server_listen_websocket_url_parses() { + let app_server = app_server_from_args( + ["codex", "app-server", "--listen", "ws://127.0.0.1:4500"].as_ref(), + ); + assert_eq!( + app_server.listen, + codex_app_server::AppServerTransport::WebSocket { + bind_address: "127.0.0.1:4500".parse().expect("valid socket address"), + } + ); + } + + #[test] + fn app_server_listen_stdio_url_parses() { + let app_server = + app_server_from_args(["codex", "app-server", "--listen", "stdio://"].as_ref()); + assert_eq!( + app_server.listen, + codex_app_server::AppServerTransport::Stdio + ); + } + + #[test] + fn app_server_listen_invalid_url_fails_to_parse() { + let parse_result = + MultitoolCli::try_parse_from(["codex", "app-server", "--listen", "http://foo"]); + assert!(parse_result.is_err()); + } + #[test] fn features_enable_parses_feature_name() { let cli = MultitoolCli::try_parse_from(["codex", "features", "enable", "unified_exec"]) From e65f76947f35beabddd16b19d7fabeaa30b70d06 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 5 Feb 2026 22:21:02 +0000 Subject: [PATCH 81/90] other announcement (#10818) --- announcement_tip.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/announcement_tip.toml b/announcement_tip.toml index e9bd9ef19f..0726fe237a 100644 --- a/announcement_tip.toml +++ b/announcement_tip.toml @@ -17,7 +17,7 @@ version_regex = "^0\\.0\\.0$" to_date = "2026-05-10" [[announcements]] -content = "**BREAKING NEWS**: `gpt-5.3-codex` is out! Upgrade to `0.98.0` to try it." +content = "**BREAKING NEWS**: `gpt-5.3-codex` is out! Upgrade to `0.98.0` for a faster, smarter, more steerable agent." from_date = "2026-02-01" to_date = "2026-02-15" version_regex = "^0\\.(?:[0-9]|[1-8][0-9]|9[0-7])\\." From 43a7290f110fc7b410e425a452c06317f6d1a56b Mon Sep 17 00:00:00 2001 From: xl-openai Date: Thu, 5 Feb 2026 14:43:31 -0800 Subject: [PATCH 82/90] Sync app-server requirements API with refreshed cloud loader (#10815) configRequirements/read now returns updated cloud requirements after login. --- .../app-server/src/codex_message_processor.rs | 4 +- codex-rs/app-server/src/config_api.rs | 47 ++++++++++++++----- codex-rs/app-server/src/message_processor.rs | 2 + 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index afcee13af9..9ffd38fa87 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -314,7 +314,7 @@ pub(crate) struct CodexMessageProcessorArgs { pub(crate) codex_linux_sandbox_exe: Option, pub(crate) config: Arc, pub(crate) cli_overrides: Vec<(String, TomlValue)>, - pub(crate) cloud_requirements: CloudRequirementsLoader, + pub(crate) cloud_requirements: Arc>, pub(crate) feedback: CodexFeedback, } @@ -360,7 +360,7 @@ impl CodexMessageProcessor { codex_linux_sandbox_exe, config, cli_overrides, - cloud_requirements: Arc::new(RwLock::new(cloud_requirements)), + cloud_requirements, conversation_listeners: HashMap::new(), listener_thread_ids_by_subscription: HashMap::new(), active_login: Arc::new(Mutex::new(None)), diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 5a43d6e528..e1f27be0b5 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -19,11 +19,16 @@ use codex_core::config_loader::ResidencyRequirement as CoreResidencyRequirement; use codex_core::config_loader::SandboxModeRequirement as CoreSandboxModeRequirement; use serde_json::json; use std::path::PathBuf; +use std::sync::Arc; +use std::sync::RwLock; use toml::Value as TomlValue; #[derive(Clone)] pub(crate) struct ConfigApi { - service: ConfigService, + codex_home: PathBuf, + cli_overrides: Vec<(String, TomlValue)>, + loader_overrides: LoaderOverrides, + cloud_requirements: Arc>, } impl ConfigApi { @@ -31,30 +36,42 @@ impl ConfigApi { codex_home: PathBuf, cli_overrides: Vec<(String, TomlValue)>, loader_overrides: LoaderOverrides, - cloud_requirements: CloudRequirementsLoader, + cloud_requirements: Arc>, ) -> Self { Self { - service: ConfigService::new( - codex_home, - cli_overrides, - loader_overrides, - cloud_requirements, - ), + codex_home, + cli_overrides, + loader_overrides, + cloud_requirements, } } + fn config_service(&self) -> ConfigService { + let cloud_requirements = self + .cloud_requirements + .read() + .map(|guard| guard.clone()) + .unwrap_or_default(); + ConfigService::new( + self.codex_home.clone(), + self.cli_overrides.clone(), + self.loader_overrides.clone(), + cloud_requirements, + ) + } + pub(crate) async fn read( &self, params: ConfigReadParams, ) -> Result { - self.service.read(params).await.map_err(map_error) + self.config_service().read(params).await.map_err(map_error) } pub(crate) async fn config_requirements_read( &self, ) -> Result { let requirements = self - .service + .config_service() .read_requirements() .await .map_err(map_error)? @@ -67,14 +84,20 @@ impl ConfigApi { &self, params: ConfigValueWriteParams, ) -> Result { - self.service.write_value(params).await.map_err(map_error) + self.config_service() + .write_value(params) + .await + .map_err(map_error) } pub(crate) async fn batch_write( &self, params: ConfigBatchWriteParams, ) -> Result { - self.service.batch_write(params).await.map_err(map_error) + self.config_service() + .batch_write(params) + .await + .map_err(map_error) } } diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 7d8da6c1b7..2646e5f0a5 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; use std::sync::Arc; +use std::sync::RwLock; use crate::codex_message_processor::CodexMessageProcessor; use crate::codex_message_processor::CodexMessageProcessorArgs; @@ -157,6 +158,7 @@ impl MessageProcessor { auth_manager.clone(), SessionSource::VSCode, )); + let cloud_requirements = Arc::new(RwLock::new(cloud_requirements)); let codex_message_processor = CodexMessageProcessor::new(CodexMessageProcessorArgs { auth_manager, thread_manager, From 378f1cabe828406d6f71c0e514775b2c01b661fe Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Thu, 5 Feb 2026 14:57:07 -0800 Subject: [PATCH 83/90] go back to auto-enabling web_search for azure (#10820) ###### What Remove special-casing that prevented auto-enabling `web_search` for Azure model provider users. Addresses #10071, #10257. ###### Why Azure fixed their responsesapi implementation; `web_search` is now supported on models it wasn't before (like `gpt-5.1-codex-max`). This request now works: ``` curl "$AZURE_API_ENDPOINT" -H "Content-Type: application/json" -H "Authorization: Bearer $AZURE_API_KEY" -d '{ "model": "gpt-5.1-codex-max", "tools": [ { "type": "web_search" } ], "tool_choice": "auto", "input": "Find the sunrise time in Paris today and cite the source." }' ``` ###### Tests Tested with above curl, removed Azure-specific tests. --- codex-rs/core/src/codex.rs | 1 - codex-rs/core/src/config/mod.rs | 16 +------ codex-rs/core/src/model_provider_info.rs | 5 --- codex-rs/core/tests/suite/web_search.rs | 56 ------------------------ 4 files changed, 2 insertions(+), 76 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index b4c2118834..326c54d5bb 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -719,7 +719,6 @@ impl Session { per_turn_config.personality = session_configuration.personality; per_turn_config.web_search_mode = Some(resolve_web_search_mode_for_turn( per_turn_config.web_search_mode, - session_configuration.provider.is_azure_responses_endpoint(), session_configuration.sandbox_policy.get(), )); per_turn_config.features = config.features.clone(); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 78f947db68..d9ddc59441 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1288,15 +1288,11 @@ fn resolve_web_search_mode( pub(crate) fn resolve_web_search_mode_for_turn( explicit_mode: Option, - is_azure_responses_endpoint: bool, sandbox_policy: &SandboxPolicy, ) -> WebSearchMode { if let Some(mode) = explicit_mode { return mode; } - if is_azure_responses_endpoint { - return WebSearchMode::Disabled; - } if matches!(sandbox_policy, SandboxPolicy::DangerFullAccess) { WebSearchMode::Live } else { @@ -2413,14 +2409,14 @@ trust_level = "trusted" #[test] fn web_search_mode_for_turn_defaults_to_cached_when_unset() { - let mode = resolve_web_search_mode_for_turn(None, false, &SandboxPolicy::ReadOnly); + let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::ReadOnly); assert_eq!(mode, WebSearchMode::Cached); } #[test] fn web_search_mode_for_turn_defaults_to_live_for_danger_full_access() { - let mode = resolve_web_search_mode_for_turn(None, false, &SandboxPolicy::DangerFullAccess); + let mode = resolve_web_search_mode_for_turn(None, &SandboxPolicy::DangerFullAccess); assert_eq!(mode, WebSearchMode::Live); } @@ -2429,20 +2425,12 @@ trust_level = "trusted" fn web_search_mode_for_turn_prefers_explicit_value() { let mode = resolve_web_search_mode_for_turn( Some(WebSearchMode::Cached), - false, &SandboxPolicy::DangerFullAccess, ); assert_eq!(mode, WebSearchMode::Cached); } - #[test] - fn web_search_mode_for_turn_disables_for_azure_responses_endpoint() { - let mode = resolve_web_search_mode_for_turn(None, true, &SandboxPolicy::DangerFullAccess); - - assert_eq!(mode, WebSearchMode::Disabled); - } - #[test] fn profile_legacy_toggles_override_base() -> std::io::Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/core/src/model_provider_info.rs b/codex-rs/core/src/model_provider_info.rs index 211822f48f..305233ae7a 100644 --- a/codex-rs/core/src/model_provider_info.rs +++ b/codex-rs/core/src/model_provider_info.rs @@ -8,7 +8,6 @@ use crate::auth::AuthMode; use crate::error::EnvVarError; use codex_api::Provider as ApiProvider; -use codex_api::is_azure_responses_wire_base_url; use codex_api::provider::RetryConfig as ApiRetryConfig; use http::HeaderMap; use http::header::HeaderName; @@ -174,10 +173,6 @@ impl ModelProviderInfo { }) } - pub(crate) fn is_azure_responses_endpoint(&self) -> bool { - is_azure_responses_wire_base_url(&self.name, self.base_url.as_deref()) - } - /// If `env_key` is Some, returns the API key for this provider if present /// (and non-empty) in the environment. If `env_key` is required but /// cannot be found, returns an error. diff --git a/codex-rs/core/tests/suite/web_search.rs b/codex-rs/core/tests/suite/web_search.rs index 8b1cc2a4e7..edcbfd35d3 100644 --- a/codex-rs/core/tests/suite/web_search.rs +++ b/codex-rs/core/tests/suite/web_search.rs @@ -1,7 +1,5 @@ #![allow(clippy::unwrap_used)] -use codex_core::WireApi; -use codex_core::built_in_model_providers; use codex_core::features::Feature; use codex_core::protocol::SandboxPolicy; use codex_protocol::config_types::WebSearchMode; @@ -22,15 +20,6 @@ fn find_web_search_tool(body: &Value) -> &Value { .expect("tools should include a web_search tool") } -#[allow(clippy::expect_used)] -fn has_web_search_tool(body: &Value) -> bool { - body["tools"] - .as_array() - .expect("request body should include tools array") - .iter() - .any(|tool| tool.get("type").and_then(Value::as_str) == Some("web_search")) -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn web_search_mode_cached_sets_external_web_access_false() { skip_if_no_network!(); @@ -198,48 +187,3 @@ async fn web_search_mode_updates_between_turns_with_sandbox_policy() { "danger-full-access policy should default web_search to live" ); } - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn web_search_mode_defaults_to_disabled_for_azure_responses() { - skip_if_no_network!(); - - let server = start_mock_server().await; - let sse = responses::sse(vec![ - responses::ev_response_created("resp-1"), - responses::ev_completed("resp-1"), - ]); - let resp_mock = responses::mount_sse_once(&server, sse).await; - - let mut builder = test_codex() - .with_model("gpt-5-codex") - .with_config(|config| { - let base_url = config.model_provider.base_url.clone(); - let mut provider = built_in_model_providers()["openai"].clone(); - provider.name = "Azure".to_string(); - provider.base_url = base_url; - provider.wire_api = WireApi::Responses; - config.model_provider_id = provider.name.clone(); - config.model_provider = provider; - config.web_search_mode = None; - config.features.disable(Feature::WebSearchCached); - config.features.disable(Feature::WebSearchRequest); - }); - let test = builder - .build(&server) - .await - .expect("create test Codex conversation"); - - test.submit_turn_with_policy( - "hello azure default web search", - SandboxPolicy::DangerFullAccess, - ) - .await - .expect("submit turn"); - - let body = resp_mock.single_request().body_json(); - assert_eq!( - has_web_search_tool(&body), - false, - "azure responses requests should disable web_search by default" - ); -} From dbe47ea01a4e2ebfb819b5bb2d1e0edd483a5ad3 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Thu, 5 Feb 2026 15:05:02 -0800 Subject: [PATCH 84/90] Send beta header with websocket connects (#10727) --- codex-rs/core/src/client.rs | 31 ++++++++++++++----- .../core/tests/suite/client_websockets.rs | 7 +++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 3631f3afb8..b8568a1043 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -82,6 +82,8 @@ use crate::model_provider_info::ModelProviderInfo; use crate::model_provider_info::WireApi; use crate::tools::spec::create_tools_json_for_responses_api; +pub const OPENAI_BETA_HEADER: &str = "OpenAI-Beta"; +pub const OPENAI_BETA_RESPONSES_WEBSOCKETS: &str = "responses_websockets=2026-02-04"; pub const X_CODEX_TURN_STATE_HEADER: &str = "x-codex-turn-state"; pub const X_CODEX_TURN_METADATA_HEADER: &str = "x-codex-turn-metadata"; pub const X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER: &str = @@ -477,14 +479,8 @@ impl ModelClientSession { }; if needs_new { - let mut headers = options.extra_headers.clone(); - headers.extend(build_conversation_headers(options.conversation_id.clone())); - if self.client.state.include_timing_metrics { - headers.insert( - X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER, - HeaderValue::from_static("true"), - ); - } + let headers = + build_websocket_connect_headers(options, self.client.state.include_timing_metrics); let websocket_telemetry = Self::build_websocket_telemetry(otel_manager); let new_conn: ApiWebSocketConnection = ApiWebSocketResponsesClient::new(api_provider, api_auth) @@ -787,6 +783,25 @@ fn build_responses_headers( headers } +fn build_websocket_connect_headers( + options: &ApiResponsesOptions, + include_timing_metrics: bool, +) -> ApiHeaderMap { + let mut headers = options.extra_headers.clone(); + headers.extend(build_conversation_headers(options.conversation_id.clone())); + headers.insert( + OPENAI_BETA_HEADER, + HeaderValue::from_static(OPENAI_BETA_RESPONSES_WEBSOCKETS), + ); + if include_timing_metrics { + headers.insert( + X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER, + HeaderValue::from_static("true"), + ); + } + headers +} + fn map_response_stream(api_stream: S, otel_manager: OtelManager) -> ResponseStream where S: futures::Stream> diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index e58a512f9e..76a1f0b3c6 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -40,6 +40,8 @@ use tempfile::TempDir; use tracing_test::traced_test; const MODEL: &str = "gpt-5.2-codex"; +const OPENAI_BETA_HEADER: &str = "OpenAI-Beta"; +const OPENAI_BETA_RESPONSES_WEBSOCKETS: &str = "responses_websockets=2026-02-04"; struct WebsocketTestHarness { _codex_home: TempDir, @@ -74,6 +76,11 @@ async fn responses_websocket_streams_request() { assert_eq!(body["model"].as_str(), Some(MODEL)); assert_eq!(body["stream"], serde_json::Value::Bool(true)); assert_eq!(body["input"].as_array().map(Vec::len), Some(1)); + let handshake = server.single_handshake(); + assert_eq!( + handshake.header(OPENAI_BETA_HEADER), + Some(OPENAI_BETA_RESPONSES_WEBSOCKETS.to_string()) + ); server.shutdown().await; } From dcea972db85b3effc17a965064cffece2dad1d1b Mon Sep 17 00:00:00 2001 From: Noah Jorgensen Date: Thu, 5 Feb 2026 15:12:27 -0800 Subject: [PATCH 85/90] updates: use brew api for version check (#10809) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `codex` currently prompts you to update via `brew upgrade --cask codex` but the brew api does not return the new version > Screenshot 2026-02-05 at 12 36
09 PM ## Solution `codex-rs/tui/src/updates.rs` was using the [latest cask in github](https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/c/codex.rb) but this does not agree with the brew api, which leads to the issue above. Instead we use the [brew api json endpoint](https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/c/codex.rb) to ensure our version check agrees with the upgrade command. --- codex-rs/tui/src/updates.rs | 50 ++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/codex-rs/tui/src/updates.rs b/codex-rs/tui/src/updates.rs index 89fd6f32f6..58d025a4a1 100644 --- a/codex-rs/tui/src/updates.rs +++ b/codex-rs/tui/src/updates.rs @@ -56,8 +56,7 @@ struct VersionInfo { const VERSION_FILENAME: &str = "version.json"; // We use the latest version from the cask if installation is via homebrew - homebrew does not immediately pick up the latest release and can lag behind. -const HOMEBREW_CASK_URL: &str = - "https://raw.githubusercontent.com/Homebrew/homebrew-cask/HEAD/Casks/c/codex.rb"; +const HOMEBREW_CASK_API_URL: &str = "https://formulae.brew.sh/api/cask/codex.json"; const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/openai/codex/releases/latest"; #[derive(Deserialize, Debug, Clone)] @@ -65,6 +64,11 @@ struct ReleaseInfo { tag_name: String, } +#[derive(Deserialize, Debug, Clone)] +struct HomebrewCaskInfo { + version: String, +} + fn version_filepath(config: &Config) -> PathBuf { config.codex_home.join(VERSION_FILENAME) } @@ -77,14 +81,14 @@ fn read_version_info(version_file: &Path) -> anyhow::Result { async fn check_for_update(version_file: &Path) -> anyhow::Result<()> { let latest_version = match update_action::get_update_action() { Some(UpdateAction::BrewUpgrade) => { - let cask_contents = create_client() - .get(HOMEBREW_CASK_URL) + let HomebrewCaskInfo { version } = create_client() + .get(HOMEBREW_CASK_API_URL) .send() .await? .error_for_status()? - .text() + .json::() .await?; - extract_version_from_cask(&cask_contents)? + version } _ => { let ReleaseInfo { @@ -123,18 +127,6 @@ fn is_newer(latest: &str, current: &str) -> Option { } } -fn extract_version_from_cask(cask_contents: &str) -> anyhow::Result { - cask_contents - .lines() - .find_map(|line| { - let line = line.trim(); - line.strip_prefix("version \"") - .and_then(|rest| rest.strip_suffix('"')) - .map(ToString::to_string) - }) - .ok_or_else(|| anyhow::anyhow!("Failed to find version in Homebrew cask file")) -} - fn extract_version_from_latest_tag(latest_tag_name: &str) -> anyhow::Result { latest_tag_name .strip_prefix("rust-v") @@ -190,16 +182,18 @@ mod tests { use super::*; #[test] - fn parses_version_from_cask_contents() { - let cask = r#" - cask "codex" do - version "0.55.0" - end - "#; - assert_eq!( - extract_version_from_cask(cask).expect("failed to parse version"), - "0.55.0" - ); + fn extract_version_from_brew_api_json() { + // + // https://formulae.brew.sh/api/cask/codex.json + let cask_json = r#"{ + "token": "codex", + "full_token": "codex", + "tap": "homebrew/cask", + "version": "0.96.0", + }"#; + let HomebrewCaskInfo { version } = serde_json::from_str::(cask_json) + .expect("failed to parse version from cask json"); + assert_eq!(version, "0.96.0"); } #[test] From 729b0165154f36de3086b111ed127ede751bdb94 Mon Sep 17 00:00:00 2001 From: Matthew Zeng Date: Thu, 5 Feb 2026 15:31:04 -0800 Subject: [PATCH 86/90] Add stage field for experimental flags. (#10793) - [x] Add stage field for experimental flags. --- .../codex_app_server_protocol.schemas.json | 76 ++++++++++++++++--- .../v2/ExperimentalFeatureListResponse.json | 76 ++++++++++++++++--- .../typescript/v2/ExperimentalFeature.ts | 16 +++- .../typescript/v2/ExperimentalFeatureStage.ts | 5 ++ .../schema/typescript/v2/index.ts | 1 + .../app-server-protocol/src/protocol/v2.rs | 29 ++++++- codex-rs/app-server/README.md | 5 +- .../app-server/src/codex_message_processor.rs | 48 ++++++++---- .../suite/v2/experimental_feature_list.rs | 42 ++++++---- 9 files changed, 236 insertions(+), 62 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureStage.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 559da3ffc4..b11f3ecf90 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 @@ -11231,37 +11231,52 @@ "ExperimentalFeature": { "properties": { "announcement": { - "description": "Announcement copy shown to users when the feature is introduced.", - "type": "string" + "description": "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] }, "defaultEnabled": { "description": "Whether this feature is enabled by default.", "type": "boolean" }, "description": { - "description": "Short summary describing what the feature does.", - "type": "string" + "description": "Short summary describing what the feature does. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] }, "displayName": { - "description": "User-facing display name shown in the experimental features UI.", - "type": "string" + "description": "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] }, "enabled": { "description": "Whether this feature is currently enabled in the loaded config.", "type": "boolean" }, - "flagName": { + "name": { "description": "Stable key used in config.toml and CLI flag toggles.", "type": "string" + }, + "stage": { + "allOf": [ + { + "$ref": "#/definitions/v2/ExperimentalFeatureStage" + } + ], + "description": "Lifecycle stage of this feature flag." } }, "required": [ - "announcement", "defaultEnabled", - "description", - "displayName", "enabled", - "flagName" + "name", + "stage" ], "type": "object" }, @@ -11311,6 +11326,45 @@ "title": "ExperimentalFeatureListResponse", "type": "object" }, + "ExperimentalFeatureStage": { + "oneOf": [ + { + "description": "Feature is available for user testing and feedback.", + "enum": [ + "beta" + ], + "type": "string" + }, + { + "description": "Feature is still being built and not ready for broad use.", + "enum": [ + "underDevelopment" + ], + "type": "string" + }, + { + "description": "Feature is production-ready.", + "enum": [ + "stable" + ], + "type": "string" + }, + { + "description": "Feature is deprecated and should be avoided.", + "enum": [ + "deprecated" + ], + "type": "string" + }, + { + "description": "Feature flag is retained only for backwards compatibility.", + "enum": [ + "removed" + ], + "type": "string" + } + ] + }, "FeedbackUploadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json index b9e36760bc..25398fc0ea 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ExperimentalFeatureListResponse.json @@ -4,39 +4,93 @@ "ExperimentalFeature": { "properties": { "announcement": { - "description": "Announcement copy shown to users when the feature is introduced.", - "type": "string" + "description": "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] }, "defaultEnabled": { "description": "Whether this feature is enabled by default.", "type": "boolean" }, "description": { - "description": "Short summary describing what the feature does.", - "type": "string" + "description": "Short summary describing what the feature does. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] }, "displayName": { - "description": "User-facing display name shown in the experimental features UI.", - "type": "string" + "description": "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] }, "enabled": { "description": "Whether this feature is currently enabled in the loaded config.", "type": "boolean" }, - "flagName": { + "name": { "description": "Stable key used in config.toml and CLI flag toggles.", "type": "string" + }, + "stage": { + "allOf": [ + { + "$ref": "#/definitions/ExperimentalFeatureStage" + } + ], + "description": "Lifecycle stage of this feature flag." } }, "required": [ - "announcement", "defaultEnabled", - "description", - "displayName", "enabled", - "flagName" + "name", + "stage" ], "type": "object" + }, + "ExperimentalFeatureStage": { + "oneOf": [ + { + "description": "Feature is available for user testing and feedback.", + "enum": [ + "beta" + ], + "type": "string" + }, + { + "description": "Feature is still being built and not ready for broad use.", + "enum": [ + "underDevelopment" + ], + "type": "string" + }, + { + "description": "Feature is production-ready.", + "enum": [ + "stable" + ], + "type": "string" + }, + { + "description": "Feature is deprecated and should be avoided.", + "enum": [ + "deprecated" + ], + "type": "string" + }, + { + "description": "Feature flag is retained only for backwards compatibility.", + "enum": [ + "removed" + ], + "type": "string" + } + ] } }, "properties": { diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts index 6275148780..e17ef83138 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeature.ts @@ -1,24 +1,32 @@ // 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 { ExperimentalFeatureStage } from "./ExperimentalFeatureStage"; export type ExperimentalFeature = { /** * Stable key used in config.toml and CLI flag toggles. */ -flagName: string, +name: string, +/** + * Lifecycle stage of this feature flag. + */ +stage: ExperimentalFeatureStage, /** * User-facing display name shown in the experimental features UI. + * Null when this feature is not in beta. */ -displayName: string, +displayName: string | null, /** * Short summary describing what the feature does. + * Null when this feature is not in beta. */ -description: string, +description: string | null, /** * Announcement copy shown to users when the feature is introduced. + * Null when this feature is not in beta. */ -announcement: string, +announcement: string | null, /** * Whether this feature is currently enabled in the loaded config. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureStage.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureStage.ts new file mode 100644 index 0000000000..dbd206e05f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExperimentalFeatureStage.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 ExperimentalFeatureStage = "beta" | "underDevelopment" | "stable" | "deprecated" | "removed"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index dfbdc58acb..faa88dfe35 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -55,6 +55,7 @@ export type { ExecPolicyAmendment } from "./ExecPolicyAmendment"; export type { ExperimentalFeature } from "./ExperimentalFeature"; export type { ExperimentalFeatureListParams } from "./ExperimentalFeatureListParams"; export type { ExperimentalFeatureListResponse } from "./ExperimentalFeatureListResponse"; +export type { ExperimentalFeatureStage } from "./ExperimentalFeatureStage"; export type { FeedbackUploadParams } from "./FeedbackUploadParams"; export type { FeedbackUploadResponse } from "./FeedbackUploadResponse"; export type { FileChangeApprovalDecision } from "./FileChangeApprovalDecision"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 73d664016f..4d4c4b7936 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1055,18 +1055,39 @@ pub struct ExperimentalFeatureListParams { pub limit: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub enum ExperimentalFeatureStage { + /// Feature is available for user testing and feedback. + Beta, + /// Feature is still being built and not ready for broad use. + UnderDevelopment, + /// Feature is production-ready. + Stable, + /// Feature is deprecated and should be avoided. + Deprecated, + /// Feature flag is retained only for backwards compatibility. + Removed, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct ExperimentalFeature { /// Stable key used in config.toml and CLI flag toggles. - pub flag_name: String, + pub name: String, + /// Lifecycle stage of this feature flag. + pub stage: ExperimentalFeatureStage, /// User-facing display name shown in the experimental features UI. - pub display_name: String, + /// Null when this feature is not in beta. + pub display_name: Option, /// Short summary describing what the feature does. - pub description: String, + /// Null when this feature is not in beta. + pub description: Option, /// Announcement copy shown to users when the feature is introduced. - pub announcement: String, + /// Null when this feature is not in beta. + pub announcement: Option, /// Whether this feature is currently enabled in the loaded config. pub enabled: bool, /// Whether this feature is enabled by default. diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 3236cb0eef..07594e2559 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -99,7 +99,7 @@ Example (from OpenAI's official VSCode extension): - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). - `model/list` — list available models (with reasoning effort options and optional `upgrade` model ids). -- `experimentalFeature/list` — list experimental feature flags with metadata (flag name, display name, description, announcement, enabled/default-enabled) and cursor pagination. +- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`. - `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). - `skills/list` — list skills for one or more `cwd` values (optional `forceReload`). - `skills/remote/read` — list public remote skills (**under development; do not call from production clients yet**). @@ -896,6 +896,7 @@ Examples of descriptor strings: - `thread/start.mockExperimentalField` (field-level gate) ### For maintainers: Adding experimental fields and methods + Use this checklist when introducing a field/method that should only be available when the client opts into experimental APIs. At runtime, clients must send `initialize` with `capabilities.experimentalApi = true` to use experimental methods or fields. @@ -916,7 +917,7 @@ At runtime, clients must send `initialize` with `capabilities.experimentalApi = # Include experimental API fields/methods in fixtures. just write-app-server-schema --experimental ``` - + 5. Verify the protocol crate: ```bash diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 9ffd38fa87..a522499bc6 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -37,6 +37,7 @@ use codex_app_server_protocol::ExecOneOffCommandResponse; use codex_app_server_protocol::ExperimentalFeature as ApiExperimentalFeature; use codex_app_server_protocol::ExperimentalFeatureListParams; use codex_app_server_protocol::ExperimentalFeatureListResponse; +use codex_app_server_protocol::ExperimentalFeatureStage as ApiExperimentalFeatureStage; use codex_app_server_protocol::FeedbackUploadParams; use codex_app_server_protocol::FeedbackUploadResponse; use codex_app_server_protocol::ForkConversationParams; @@ -3262,23 +3263,40 @@ impl CodexMessageProcessor { let data = FEATURES .iter() - .filter_map(|spec| { - let Stage::Experimental { - name, - menu_description, - announcement, - } = spec.stage - else { - return None; + .map(|spec| { + let (stage, display_name, description, announcement) = match spec.stage { + Stage::Experimental { + name, + menu_description, + announcement, + } => ( + ApiExperimentalFeatureStage::Beta, + Some(name.to_string()), + Some(menu_description.to_string()), + Some(announcement.to_string()), + ), + Stage::UnderDevelopment => ( + ApiExperimentalFeatureStage::UnderDevelopment, + None, + None, + None, + ), + Stage::Stable => (ApiExperimentalFeatureStage::Stable, None, None, None), + Stage::Deprecated => { + (ApiExperimentalFeatureStage::Deprecated, None, None, None) + } + Stage::Removed => (ApiExperimentalFeatureStage::Removed, None, None, None), }; - Some(ApiExperimentalFeature { - flag_name: spec.key.to_string(), - display_name: name.to_string(), - description: menu_description.to_string(), - announcement: announcement.to_string(), + + ApiExperimentalFeature { + name: spec.key.to_string(), + stage, + display_name, + description, + announcement, enabled: config.features.enabled(spec.id), default_enabled: spec.default_enabled, - }) + } }) .collect::>(); @@ -3317,7 +3335,7 @@ impl CodexMessageProcessor { if start > total { self.send_invalid_request_error( request_id, - format!("cursor {start} exceeds total experimental features {total}"), + format!("cursor {start} exceeds total feature flags {total}"), ) .await; return; diff --git a/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs b/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs index 2ca236d89e..fdcbaca5b0 100644 --- a/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs +++ b/codex-rs/app-server/tests/suite/v2/experimental_feature_list.rs @@ -6,6 +6,7 @@ use app_test_support::to_response; use codex_app_server_protocol::ExperimentalFeature; use codex_app_server_protocol::ExperimentalFeatureListParams; use codex_app_server_protocol::ExperimentalFeatureListResponse; +use codex_app_server_protocol::ExperimentalFeatureStage; use codex_app_server_protocol::JSONRPCResponse; use codex_app_server_protocol::RequestId; use codex_core::features::FEATURES; @@ -17,7 +18,7 @@ use tokio::time::timeout; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); #[tokio::test] -async fn experimental_feature_list_returns_experimental_feature_metadata() -> Result<()> { +async fn experimental_feature_list_returns_feature_metadata_with_stage() -> Result<()> { let codex_home = TempDir::new()?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -36,24 +37,35 @@ async fn experimental_feature_list_returns_experimental_feature_metadata() -> Re let actual = to_response::(response)?; let expected_data = FEATURES .iter() - .filter_map(|spec| { - let Stage::Experimental { - name, - menu_description, - announcement, - } = spec.stage - else { - return None; + .map(|spec| { + let (stage, display_name, description, announcement) = match spec.stage { + Stage::Experimental { + name, + menu_description, + announcement, + } => ( + ExperimentalFeatureStage::Beta, + Some(name.to_string()), + Some(menu_description.to_string()), + Some(announcement.to_string()), + ), + Stage::UnderDevelopment => { + (ExperimentalFeatureStage::UnderDevelopment, None, None, None) + } + Stage::Stable => (ExperimentalFeatureStage::Stable, None, None, None), + Stage::Deprecated => (ExperimentalFeatureStage::Deprecated, None, None, None), + Stage::Removed => (ExperimentalFeatureStage::Removed, None, None, None), }; - Some(ExperimentalFeature { - flag_name: spec.key.to_string(), - display_name: name.to_string(), - description: menu_description.to_string(), - announcement: announcement.to_string(), + ExperimentalFeature { + name: spec.key.to_string(), + stage, + display_name, + description, + announcement, enabled: spec.default_enabled, default_enabled: spec.default_enabled, - }) + } }) .collect::>(); let expected = ExperimentalFeatureListResponse { From 0d8b2b74c46aeb7c691fcaf96156ed7927ee1d16 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Thu, 5 Feb 2026 16:35:04 -0800 Subject: [PATCH 87/90] feat(app-server): turn/steer API (#10821) This PR adds a dedicated `turn/steer` API for appending user input to an in-flight turn. ## Motivation Currently, steering in the app is implemented by just calling `turn/start` while a turn is running. This has some really weird quirks: - Client gets back a new `turn.id`, even though streamed events/approvals remained tied to the original active turn ID. - All the various turn-level override params on `turn/start` do not apply to the "steer", and would only apply to the next real turn. - There can also be a race condition where the client thinks the turn is active but the server has already completed it, so there might be bugs if the client has baked in some client-specific behavior thinking it's a steer when in fact the server kicked off a new turn. This is particularly possible when running a client against a remote app-server. Having a dedicated `turn/steer` API eliminates all those quirks. `turn/steer` behavior: - Requires an active turn on threadId. Returns a JSON-RPC error if there is no active turn. - If expectedTurnId is provided, it must match the active turn (more useful when connecting to a remote app-server). - Does not emit `turn/started`. - Does not accept turn overrides (`cwd`, `model`, `sandbox`, etc.) or `outputSchema` to accurately reflect that these are not applied when steering. --- .../schema/json/ClientRequest.json | 47 +++++ .../codex_app_server_protocol.schemas.json | 62 ++++++ .../schema/json/v2/TurnSteerParams.json | 189 ++++++++++++++++++ .../schema/json/v2/TurnSteerResponse.json | 13 ++ .../schema/typescript/ClientRequest.ts | 3 +- .../schema/typescript/v2/TurnSteerParams.ts | 11 + .../schema/typescript/v2/TurnSteerResponse.ts | 5 + .../schema/typescript/v2/index.ts | 2 + .../src/protocol/common.rs | 4 + .../app-server-protocol/src/protocol/v2.rs | 18 ++ codex-rs/app-server/README.md | 17 ++ .../app-server/src/codex_message_processor.rs | 64 ++++++ .../app-server/tests/common/mcp_process.rs | 10 + codex-rs/app-server/tests/suite/v2/mod.rs | 1 + .../app-server/tests/suite/v2/turn_steer.rs | 179 +++++++++++++++++ codex-rs/core/src/codex.rs | 145 ++++++++++++-- codex-rs/core/src/codex_thread.rs | 10 + codex-rs/core/src/lib.rs | 1 + 18 files changed, 768 insertions(+), 13 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json create mode 100644 codex-rs/app-server-protocol/schema/json/v2/TurnSteerResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerResponse.ts create mode 100644 codex-rs/app-server/tests/suite/v2/turn_steer.rs diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index bffc926914..0ccf453020 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -2810,6 +2810,29 @@ ], "type": "object" }, + "TurnSteerParams": { + "properties": { + "expectedTurnId": { + "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", + "type": "string" + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "expectedTurnId", + "input", + "threadId" + ], + "type": "object" + }, "UserInput": { "oneOf": [ { @@ -3511,6 +3534,30 @@ "title": "Turn/startRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/steer" + ], + "title": "Turn/steerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/TurnSteerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/steerRequest", + "type": "object" + }, { "properties": { "id": { 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 b11f3ecf90..3b6e61fc23 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 @@ -862,6 +862,30 @@ "title": "Turn/startRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "turn/steer" + ], + "title": "Turn/steerRequestMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/TurnSteerParams" + } + }, + "required": [ + "id", + "method", + "params" + ], + "title": "Turn/steerRequest", + "type": "object" + }, { "properties": { "id": { @@ -15729,6 +15753,44 @@ ], "type": "string" }, + "TurnSteerParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "expectedTurnId": { + "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", + "type": "string" + }, + "input": { + "items": { + "$ref": "#/definitions/v2/UserInput" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "expectedTurnId", + "input", + "threadId" + ], + "title": "TurnSteerParams", + "type": "object" + }, + "TurnSteerResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "turnId": { + "type": "string" + } + }, + "required": [ + "turnId" + ], + "title": "TurnSteerResponse", + "type": "object" + }, "UserInput": { "oneOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json new file mode 100644 index 0000000000..a064d9e7e3 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerParams.json @@ -0,0 +1,189 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ByteRange": { + "properties": { + "end": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + }, + "start": { + "format": "uint", + "minimum": 0.0, + "type": "integer" + } + }, + "required": [ + "end", + "start" + ], + "type": "object" + }, + "TextElement": { + "properties": { + "byteRange": { + "allOf": [ + { + "$ref": "#/definitions/ByteRange" + } + ], + "description": "Byte range in the parent `text` buffer that this element occupies." + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "byteRange" + ], + "type": "object" + }, + "UserInput": { + "oneOf": [ + { + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "default": [], + "description": "UI-defined spans within `text` used to render or persist special elements.", + "items": { + "$ref": "#/definitions/TextElement" + }, + "type": "array" + }, + "type": { + "enum": [ + "text" + ], + "title": "TextUserInputType", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "title": "TextUserInput", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "image" + ], + "title": "ImageUserInputType", + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "ImageUserInput", + "type": "object" + }, + { + "properties": { + "path": { + "type": "string" + }, + "type": { + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "LocalImageUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "skill" + ], + "title": "SkillUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "SkillUserInput", + "type": "object" + }, + { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "enum": [ + "mention" + ], + "title": "MentionUserInputType", + "type": "string" + } + }, + "required": [ + "name", + "path", + "type" + ], + "title": "MentionUserInput", + "type": "object" + } + ] + } + }, + "properties": { + "expectedTurnId": { + "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", + "type": "string" + }, + "input": { + "items": { + "$ref": "#/definitions/UserInput" + }, + "type": "array" + }, + "threadId": { + "type": "string" + } + }, + "required": [ + "expectedTurnId", + "input", + "threadId" + ], + "title": "TurnSteerParams", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnSteerResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerResponse.json new file mode 100644 index 0000000000..d801a3613c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnSteerResponse.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "turnId": { + "type": "string" + } + }, + "required": [ + "turnId" + ], + "title": "TurnSteerResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index 04d52f03bc..3b5f6787b1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -52,8 +52,9 @@ import type { ThreadStartParams } from "./v2/ThreadStartParams"; import type { ThreadUnarchiveParams } from "./v2/ThreadUnarchiveParams"; import type { TurnInterruptParams } from "./v2/TurnInterruptParams"; import type { TurnStartParams } from "./v2/TurnStartParams"; +import type { TurnSteerParams } from "./v2/TurnSteerParams"; /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts new file mode 100644 index 0000000000..2c84f195cf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerParams.ts @@ -0,0 +1,11 @@ +// 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 { UserInput } from "./UserInput"; + +export type TurnSteerParams = { threadId: string, input: Array, +/** + * Required active turn id precondition. The request fails when it does not + * match the currently active turn. + */ +expectedTurnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerResponse.ts new file mode 100644 index 0000000000..390adb4f59 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnSteerResponse.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 TurnSteerResponse = { turnId: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index faa88dfe35..65a12daa34 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -180,6 +180,8 @@ export type { TurnStartParams } from "./TurnStartParams"; export type { TurnStartResponse } from "./TurnStartResponse"; export type { TurnStartedNotification } from "./TurnStartedNotification"; export type { TurnStatus } from "./TurnStatus"; +export type { TurnSteerParams } from "./TurnSteerParams"; +export type { TurnSteerResponse } from "./TurnSteerResponse"; export type { UserInput } from "./UserInput"; export type { WebSearchAction } from "./WebSearchAction"; export type { WindowsWorldWritableWarningNotification } from "./WindowsWorldWritableWarningNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 912f4d58ac..46de0e5817 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -252,6 +252,10 @@ client_request_definitions! { params: v2::TurnStartParams, response: v2::TurnStartResponse, }, + TurnSteer => "turn/steer" { + params: v2::TurnSteerParams, + response: v2::TurnSteerResponse, + }, TurnInterrupt => "turn/interrupt" { params: v2::TurnInterruptParams, response: v2::TurnInterruptResponse, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 4d4c4b7936..a978fec6db 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -2092,6 +2092,24 @@ pub struct TurnStartResponse { pub turn: Turn, } +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnSteerParams { + pub thread_id: String, + pub input: Vec, + /// Required active turn id precondition. The request fails when it does not + /// match the currently active turn. + pub expected_turn_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct TurnSteerResponse { + pub turn_id: String, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 07594e2559..a6d49f2e4c 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -95,6 +95,7 @@ Example (from OpenAI's official VSCode extension): - `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications. - `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success. - `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. +- `turn/steer` — add user input to an already in-flight turn without starting a new turn; returns the active `turnId` that accepted the input. - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. - `review/start` — kick off Codex’s automated reviewer for a thread; responds like `turn/start` and emits `item/started`/`item/completed` notifications with `enteredReviewMode` and `exitedReviewMode` items, plus a final assistant `agentMessage` containing the review. - `command/exec` — run a single command under the server sandbox without starting a thread/turn (handy for utilities and validation). @@ -363,6 +364,22 @@ You can cancel a running Turn with `turn/interrupt`. The server requests cancellations for running subprocesses, then emits a `turn/completed` event with `status: "interrupted"`. Rely on the `turn/completed` to know when Codex-side cleanup is done. +### Example: Steer an active turn + +Use `turn/steer` to append additional user input to the currently active turn. This does not emit +`turn/started` and does not accept turn context overrides. + +```json +{ "method": "turn/steer", "id": 32, "params": { + "threadId": "thr_123", + "input": [ { "type": "text", "text": "Actually focus on failing tests first." } ], + "expectedTurnId": "turn_456" +} } +{ "id": 32, "result": { "turnId": "turn_456" } } +``` + +`expectedTurnId` is required. If there is no active turn (or `expectedTurnId` does not match the active turn), the request fails with an `invalid request` error. + ### Example: Request a code review Use `review/start` to run Codex’s reviewer on the currently checked-out project. The request takes the thread id plus a `target` describing what should be reviewed: diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index a522499bc6..f96daebc19 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -139,6 +139,8 @@ use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStartedNotification; use codex_app_server_protocol::TurnStatus; +use codex_app_server_protocol::TurnSteerParams; +use codex_app_server_protocol::TurnSteerResponse; use codex_app_server_protocol::UserInfoResponse; use codex_app_server_protocol::UserInput as V2UserInput; use codex_app_server_protocol::UserSavedConfig; @@ -154,6 +156,7 @@ use codex_core::InitialHistory; use codex_core::NewThread; use codex_core::RolloutRecorder; use codex_core::SessionMeta; +use codex_core::SteerInputError; use codex_core::ThreadConfigSnapshot; use codex_core::ThreadManager; use codex_core::ThreadSortKey as CoreThreadSortKey; @@ -532,6 +535,10 @@ impl CodexMessageProcessor { self.turn_start(to_connection_request_id(request_id), params) .await; } + ClientRequest::TurnSteer { request_id, params } => { + self.turn_steer(to_connection_request_id(request_id), params) + .await; + } ClientRequest::TurnInterrupt { request_id, params } => { self.turn_interrupt(to_connection_request_id(request_id), params) .await; @@ -4620,6 +4627,63 @@ impl CodexMessageProcessor { } } + async fn turn_steer(&self, request_id: ConnectionRequestId, params: TurnSteerParams) { + let (_, thread) = match self.load_thread(¶ms.thread_id).await { + Ok(v) => v, + Err(error) => { + self.outgoing.send_error(request_id, error).await; + return; + } + }; + + if params.expected_turn_id.is_empty() { + self.send_invalid_request_error( + request_id, + "expectedTurnId must not be empty".to_string(), + ) + .await; + return; + } + + let mapped_items: Vec = params + .input + .into_iter() + .map(V2UserInput::into_core) + .collect(); + + match thread + .steer_input(mapped_items, Some(¶ms.expected_turn_id)) + .await + { + Ok(turn_id) => { + let response = TurnSteerResponse { turn_id }; + self.outgoing.send_response(request_id, response).await; + } + Err(err) => { + let (code, message) = match err { + SteerInputError::NoActiveTurn(_) => ( + INVALID_REQUEST_ERROR_CODE, + "no active turn to steer".to_string(), + ), + SteerInputError::ExpectedTurnMismatch { expected, actual } => ( + INVALID_REQUEST_ERROR_CODE, + format!("expected active turn id `{expected}` but found `{actual}`"), + ), + SteerInputError::EmptyInput => ( + INVALID_REQUEST_ERROR_CODE, + "input must not be empty".to_string(), + ), + }; + let error = JSONRPCErrorError { + code, + message, + data: None, + }; + self.outgoing.send_error(request_id, error).await; + } + } + } + fn build_review_turn(turn_id: String, display_text: &str) -> Turn { let items = if display_text.is_empty() { Vec::new() diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index d992db6d45..57c29fcf9f 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -62,6 +62,7 @@ use codex_app_server_protocol::ThreadStartParams; use codex_app_server_protocol::ThreadUnarchiveParams; use codex_app_server_protocol::TurnInterruptParams; use codex_app_server_protocol::TurnStartParams; +use codex_app_server_protocol::TurnSteerParams; use codex_core::default_client::CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR; use tokio::process::Command; @@ -557,6 +558,15 @@ impl McpProcess { self.send_request("turn/interrupt", params).await } + /// Send a `turn/steer` JSON-RPC request (v2). + pub async fn send_turn_steer_request( + &mut self, + params: TurnSteerParams, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(params)?); + self.send_request("turn/steer", params).await + } + /// Send a `review/start` JSON-RPC request (v2). pub async fn send_review_start_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 0894504a0f..c25a6d0568 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -26,3 +26,4 @@ mod thread_start; mod thread_unarchive; mod turn_interrupt; mod turn_start; +mod turn_steer; diff --git a/codex-rs/app-server/tests/suite/v2/turn_steer.rs b/codex-rs/app-server/tests/suite/v2/turn_steer.rs new file mode 100644 index 0000000000..89704326fd --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/turn_steer.rs @@ -0,0 +1,179 @@ +#![cfg(unix)] + +use anyhow::Result; +use app_test_support::McpProcess; +use app_test_support::create_mock_responses_server_sequence; +use app_test_support::create_mock_responses_server_sequence_unchecked; +use app_test_support::create_shell_command_sse_response; +use app_test_support::to_response; +use codex_app_server_protocol::JSONRPCError; +use codex_app_server_protocol::JSONRPCNotification; +use codex_app_server_protocol::JSONRPCResponse; +use codex_app_server_protocol::RequestId; +use codex_app_server_protocol::ThreadStartParams; +use codex_app_server_protocol::ThreadStartResponse; +use codex_app_server_protocol::TurnStartParams; +use codex_app_server_protocol::TurnStartResponse; +use codex_app_server_protocol::TurnSteerParams; +use codex_app_server_protocol::TurnSteerResponse; +use codex_app_server_protocol::UserInput as V2UserInput; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +#[tokio::test] +async fn turn_steer_requires_active_turn() -> Result<()> { + let tmp = TempDir::new()?; + let codex_home = tmp.path().join("codex_home"); + std::fs::create_dir(&codex_home)?; + + let server = create_mock_responses_server_sequence(vec![]).await; + create_config_toml(&codex_home, &server.uri())?; + + let mut mcp = McpProcess::new(&codex_home).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let steer_req = mcp + .send_turn_steer_request(TurnSteerParams { + thread_id: thread.id, + input: vec![V2UserInput::Text { + text: "steer".to_string(), + text_elements: Vec::new(), + }], + expected_turn_id: "turn-does-not-exist".to_string(), + }) + .await?; + let steer_err: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(steer_req)), + ) + .await??; + assert_eq!(steer_err.error.code, -32600); + + Ok(()) +} + +#[tokio::test] +async fn turn_steer_returns_active_turn_id() -> Result<()> { + #[cfg(target_os = "windows")] + let shell_command = vec![ + "powershell".to_string(), + "-Command".to_string(), + "Start-Sleep -Seconds 10".to_string(), + ]; + #[cfg(not(target_os = "windows"))] + let shell_command = vec!["sleep".to_string(), "10".to_string()]; + + let tmp = TempDir::new()?; + let codex_home = tmp.path().join("codex_home"); + std::fs::create_dir(&codex_home)?; + let working_directory = tmp.path().join("workdir"); + std::fs::create_dir(&working_directory)?; + + let server = + create_mock_responses_server_sequence_unchecked(vec![create_shell_command_sse_response( + shell_command.clone(), + Some(&working_directory), + Some(10_000), + "call_sleep", + )?]) + .await; + create_config_toml(&codex_home, &server.uri())?; + + let mut mcp = McpProcess::new(&codex_home).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id.clone(), + input: vec![V2UserInput::Text { + text: "run sleep".to_string(), + text_elements: Vec::new(), + }], + cwd: Some(working_directory.clone()), + ..Default::default() + }) + .await?; + let turn_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + let TurnStartResponse { turn } = to_response::(turn_resp)?; + + let _task_started: JSONRPCNotification = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("codex/event/task_started"), + ) + .await??; + + let steer_req = mcp + .send_turn_steer_request(TurnSteerParams { + thread_id: thread.id, + input: vec![V2UserInput::Text { + text: "steer".to_string(), + text_elements: Vec::new(), + }], + expected_turn_id: turn.id.clone(), + }) + .await?; + let steer_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(steer_req)), + ) + .await??; + let steer: TurnSteerResponse = to_response::(steer_resp)?; + assert_eq!(steer.turn_id, turn.id); + + Ok(()) +} + +fn create_config_toml(codex_home: &std::path::Path, server_uri: &str) -> std::io::Result<()> { + let config_toml = codex_home.join("config.toml"); + std::fs::write( + config_toml, + format!( + r#" +model = "mock-model" +approval_policy = "never" +sandbox_mode = "danger-full-access" + +model_provider = "mock_provider" + +[model_providers.mock_provider] +name = "Mock provider for test" +base_url = "{server_uri}/v1" +wire_api = "responses" +request_max_retries = 0 +stream_max_retries = 0 +"# + ), + ) +} diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 326c54d5bb..0ddbf426ea 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -118,6 +118,13 @@ use crate::error::CodexErr; use crate::error::Result as CodexResult; #[cfg(test)] use crate::exec::StreamOutput; + +#[derive(Debug, PartialEq)] +pub enum SteerInputError { + NoActiveTurn(Vec), + ExpectedTurnMismatch { expected: String, actual: String }, + EmptyInput, +} use crate::exec_policy::ExecPolicyUpdateError; use crate::feedback_tags; use crate::file_watcher::FileWatcher; @@ -455,6 +462,14 @@ impl Codex { Ok(event) } + pub async fn steer_input( + &self, + input: Vec, + expected_turn_id: Option<&str>, + ) -> Result { + self.session.steer_input(input, expected_turn_id).await + } + pub(crate) async fn agent_status(&self) -> AgentStatus { self.agent_status.borrow().clone() } @@ -2327,17 +2342,39 @@ impl Session { .await; } - /// Returns the input if there was no task running to inject into - pub async fn inject_input(&self, input: Vec) -> Result<(), Vec> { - let mut active = self.active_turn.lock().await; - match active.as_mut() { - Some(at) => { - let mut ts = at.turn_state.lock().await; - ts.push_pending_input(input.into()); - Ok(()) - } - None => Err(input), + /// Inject additional user input into the currently active turn. + /// + /// Returns the active turn id when accepted. + pub async fn steer_input( + &self, + input: Vec, + expected_turn_id: Option<&str>, + ) -> Result { + if input.is_empty() { + return Err(SteerInputError::EmptyInput); } + + let mut active = self.active_turn.lock().await; + let Some(active_turn) = active.as_mut() else { + return Err(SteerInputError::NoActiveTurn(input)); + }; + + let Some((active_turn_id, _)) = active_turn.tasks.first() else { + return Err(SteerInputError::NoActiveTurn(input)); + }; + + if let Some(expected_turn_id) = expected_turn_id + && expected_turn_id != active_turn_id + { + return Err(SteerInputError::ExpectedTurnMismatch { + expected: expected_turn_id.to_string(), + actual: active_turn_id.clone(), + }); + } + + let mut turn_state = active_turn.turn_state.lock().await; + turn_state.push_pending_input(input.into()); + Ok(active_turn_id.clone()) } /// Returns the input if there was no task running to inject into @@ -2716,6 +2753,7 @@ async fn submission_loop(sess: Arc, config: Arc, rx_sub: Receiv mod handlers { use crate::codex::Session; use crate::codex::SessionSettingsUpdate; + use crate::codex::SteerInputError; use crate::codex::TurnContext; use crate::codex::spawn_review_thread; @@ -2850,8 +2888,8 @@ mod handlers { }; current_context.otel_manager.user_prompt(&items); - // Attempt to inject input into current task - if let Err(items) = sess.inject_input(items).await { + // Attempt to inject input into current task. + if let Err(SteerInputError::NoActiveTurn(items)) = sess.steer_input(items, None).await { sess.seed_initial_context_if_needed(¤t_context).await; let resumed_model = sess.take_pending_resume_previous_model().await; let update_items = sess.build_settings_update_items( @@ -6123,6 +6161,89 @@ mod tests { ); } + #[tokio::test] + async fn steer_input_requires_active_turn() { + let (sess, _tc, _rx) = make_session_and_context_with_rx().await; + let input = vec![UserInput::Text { + text: "steer".to_string(), + text_elements: Vec::new(), + }]; + + let err = sess + .steer_input(input, None) + .await + .expect_err("steering without active turn should fail"); + + assert!(matches!(err, SteerInputError::NoActiveTurn(_))); + } + + #[tokio::test] + async fn steer_input_enforces_expected_turn_id() { + let (sess, tc, _rx) = make_session_and_context_with_rx().await; + let input = vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }]; + sess.spawn_task( + Arc::clone(&tc), + input, + NeverEndingTask { + kind: TaskKind::Regular, + listen_to_cancellation_token: false, + }, + ) + .await; + + let steer_input = vec![UserInput::Text { + text: "steer".to_string(), + text_elements: Vec::new(), + }]; + let err = sess + .steer_input(steer_input, Some("different-turn-id")) + .await + .expect_err("mismatched expected turn id should fail"); + + match err { + SteerInputError::ExpectedTurnMismatch { expected, actual } => { + assert_eq!( + (expected, actual), + ("different-turn-id".to_string(), tc.sub_id.clone()) + ); + } + other => panic!("unexpected error: {other:?}"), + } + } + + #[tokio::test] + async fn steer_input_returns_active_turn_id() { + let (sess, tc, _rx) = make_session_and_context_with_rx().await; + let input = vec![UserInput::Text { + text: "hello".to_string(), + text_elements: Vec::new(), + }]; + sess.spawn_task( + Arc::clone(&tc), + input, + NeverEndingTask { + kind: TaskKind::Regular, + listen_to_cancellation_token: false, + }, + ) + .await; + + let steer_input = vec![UserInput::Text { + text: "steer".to_string(), + text_elements: Vec::new(), + }]; + let turn_id = sess + .steer_input(steer_input, Some(&tc.sub_id)) + .await + .expect("steering with matching expected turn id should succeed"); + + assert_eq!(turn_id, tc.sub_id); + assert!(sess.has_pending_input().await); + } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn abort_review_task_emits_exited_then_aborted_and_records_history() { let (sess, tc, rx) = make_session_and_context_with_rx().await; diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index fb8e466d71..0c0bbe0e0d 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -1,5 +1,6 @@ use crate::agent::AgentStatus; use crate::codex::Codex; +use crate::codex::SteerInputError; use crate::error::Result as CodexResult; use crate::protocol::Event; use crate::protocol::Op; @@ -9,6 +10,7 @@ use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; +use codex_protocol::user_input::UserInput; use std::path::PathBuf; use tokio::sync::watch; @@ -45,6 +47,14 @@ impl CodexThread { self.codex.submit(op).await } + pub async fn steer_input( + &self, + input: Vec, + expected_turn_id: Option<&str>, + ) -> Result { + self.codex.steer_input(input, expected_turn_id).await + } + /// Use sparingly: this is intended to be removed soon. pub async fn submit_with_id(&self, sub: Submission) -> CodexResult<()> { self.codex.submit_with_id(sub).await diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index ff54bdc80c..f1534cf0f7 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -13,6 +13,7 @@ pub mod bash; mod client; mod client_common; pub mod codex; +pub use codex::SteerInputError; mod codex_thread; mod compact_remote; pub use codex_thread::CodexThread; From d74fa8edd167c7f95f1e01add447a53fb33ef6a9 Mon Sep 17 00:00:00 2001 From: gt-oai Date: Fri, 6 Feb 2026 01:12:44 +0000 Subject: [PATCH 88/90] Print warning when config does not meet requirements (#10792) Screenshot 2026-02-05 at 23 34 08 Currently, if a config value is set that fails the requirements, we exit Codex. Now, instead of this, we print a warning and default to a requirements-permitting value. --- codex-rs/core/src/codex.rs | 8 ++ codex-rs/core/src/config/mod.rs | 87 ++++++++++++++++------ codex-rs/core/src/tools/handlers/collab.rs | 41 +++++++++- 3 files changed, 112 insertions(+), 24 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 0ddbf426ea..572093d84a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -961,6 +961,14 @@ impl Session { }), }); } + for message in &config.startup_warnings { + post_session_configured_events.push(Event { + id: "".to_owned(), + msg: EventMsg::Warning(WarningEvent { + message: message.clone(), + }), + }); + } maybe_push_unstable_features_warning(&config, &mut post_session_configured_events); let auth = auth.as_ref(); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index d9ddc59441..7de6f00ea8 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -21,6 +21,7 @@ use crate::config::types::UriBasedFileOpener; use crate::config_loader::CloudRequirementsLoader; use crate::config_loader::ConfigLayerStack; use crate::config_loader::ConfigRequirements; +use crate::config_loader::ConstrainedWithSource; use crate::config_loader::LoaderOverrides; use crate::config_loader::McpServerIdentity; use crate::config_loader::McpServerRequirement; @@ -116,6 +117,9 @@ pub struct Config { /// requirements). pub config_layer_stack: ConfigLayerStack, + /// Warnings collected during config load that should be shown on startup. + pub startup_warnings: Vec, + /// Optional override of model selection. pub model: Option, @@ -601,6 +605,41 @@ fn constrain_mcp_servers( }) } +fn apply_requirement_constrained_value( + field_name: &'static str, + configured_value: T, + constrained_value: &mut ConstrainedWithSource, + startup_warnings: &mut Vec, +) -> std::io::Result<()> +where + T: Clone + std::fmt::Debug + Send + Sync, +{ + if let Err(err) = constrained_value.set(configured_value) { + let fallback_value = constrained_value.get().clone(); + tracing::warn!( + error = %err, + ?fallback_value, + requirement_source = ?constrained_value.source, + "configured value is disallowed by requirements; falling back to required value for {field_name}" + ); + let message = format!( + "Configured value for `{field_name}` is disallowed by requirements; falling back to required value {fallback_value:?}. Details: {err}" + ); + startup_warnings.push(message); + + constrained_value.set(fallback_value).map_err(|fallback_err| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!( + "configured value for `{field_name}` is disallowed by requirements ({err}); fallback to a requirement-compliant value also failed ({fallback_err})" + ), + ) + })?; + } + + Ok(()) +} + fn mcp_server_matches_requirement( requirement: &McpServerRequirement, server: &McpServerConfig, @@ -1320,6 +1359,7 @@ impl Config { ) -> std::io::Result { let requirements = config_layer_stack.requirements().clone(); let user_instructions = Self::load_instructions(Some(&codex_home)); + let mut startup_warnings = Vec::new(); // Destructure ConfigOverrides fully to ensure all overrides are applied. let ConfigOverrides { @@ -1586,12 +1626,18 @@ impl Config { enforce_residency, } = requirements; - constrained_approval_policy - .set(approval_policy) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?; - constrained_sandbox_policy - .set(sandbox_policy) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?; + apply_requirement_constrained_value( + "approval_policy", + approval_policy, + &mut constrained_approval_policy, + &mut startup_warnings, + )?; + apply_requirement_constrained_value( + "sandbox_mode", + sandbox_policy, + &mut constrained_sandbox_policy, + &mut startup_warnings, + )?; let mcp_servers = constrain_mcp_servers(cfg.mcp_servers.clone(), mcp_servers.as_ref()) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?; @@ -1604,6 +1650,7 @@ impl Config { model_provider_id, model_provider, cwd: resolved_cwd, + startup_warnings, approval_policy: constrained_approval_policy.value, sandbox_policy: constrained_sandbox_policy.value, enforce_residency: enforce_residency.value, @@ -3853,6 +3900,7 @@ model_verbosity = "high" codex_home: fixture.codex_home(), log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), + startup_warnings: Vec::new(), history: History::default(), ephemeral: false, file_opener: UriBasedFileOpener::VsCode, @@ -3940,6 +3988,7 @@ model_verbosity = "high" codex_home: fixture.codex_home(), log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), + startup_warnings: Vec::new(), history: History::default(), ephemeral: false, file_opener: UriBasedFileOpener::VsCode, @@ -4042,6 +4091,7 @@ model_verbosity = "high" codex_home: fixture.codex_home(), log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), + startup_warnings: Vec::new(), history: History::default(), ephemeral: false, file_opener: UriBasedFileOpener::VsCode, @@ -4130,6 +4180,7 @@ model_verbosity = "high" codex_home: fixture.codex_home(), log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), + startup_warnings: Vec::new(), history: History::default(), ephemeral: false, file_opener: UriBasedFileOpener::VsCode, @@ -4681,7 +4732,7 @@ mcp_oauth_callback_port = 5678 } #[tokio::test] - async fn explicit_sandbox_mode_still_errors_when_disallowed_by_requirements() + async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> std::io::Result<()> { let codex_home = TempDir::new()?; std::fs::write( @@ -4700,19 +4751,15 @@ mcp_oauth_callback_port = 5678 enforce_residency: None, }; - let err = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .fallback_cwd(Some(codex_home.path().to_path_buf())) .cloud_requirements(CloudRequirementsLoader::new( async move { Some(requirements) }, )) .build() - .await - .expect_err("explicit disallowed mode should still fail"); - assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); - let message = err.to_string(); - assert!(message.contains("invalid value for `sandbox_mode`")); - assert!(message.contains("set by cloud requirements")); + .await?; + assert_eq!(*config.sandbox_policy.get(), SandboxPolicy::ReadOnly); Ok(()) } @@ -4749,7 +4796,7 @@ trust_level = "untrusted" } #[tokio::test] - async fn explicit_approval_policy_still_errors_when_disallowed_by_requirements() + async fn explicit_approval_policy_falls_back_when_disallowed_by_requirements() -> std::io::Result<()> { let codex_home = TempDir::new()?; std::fs::write( @@ -4758,7 +4805,7 @@ trust_level = "untrusted" "#, )?; - let err = ConfigBuilder::default() + let config = ConfigBuilder::default() .codex_home(codex_home.path().to_path_buf()) .fallback_cwd(Some(codex_home.path().to_path_buf())) .cloud_requirements(CloudRequirementsLoader::new(async { @@ -4768,12 +4815,8 @@ trust_level = "untrusted" }) })) .build() - .await - .expect_err("explicit disallowed approval policy should fail"); - assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); - let message = err.to_string(); - assert!(message.contains("invalid value for `approval_policy`")); - assert!(message.contains("set by cloud requirements")); + .await?; + assert_eq!(config.approval_policy.value(), AskForApproval::OnRequest); Ok(()) } } diff --git a/codex-rs/core/src/tools/handlers/collab.rs b/codex-rs/core/src/tools/handlers/collab.rs index 3110204d29..e6f0c3c959 100644 --- a/codex-rs/core/src/tools/handlers/collab.rs +++ b/codex-rs/core/src/tools/handlers/collab.rs @@ -1168,6 +1168,37 @@ mod tests { #[tokio::test] async fn build_agent_spawn_config_uses_turn_context_values() { + fn pick_allowed_approval_policy( + constraint: &crate::config::Constrained, + base: AskForApproval, + ) -> AskForApproval { + let candidates = [ + AskForApproval::Never, + AskForApproval::UnlessTrusted, + AskForApproval::OnRequest, + AskForApproval::OnFailure, + ]; + candidates + .into_iter() + .find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok()) + .unwrap_or(base) + } + + fn pick_allowed_sandbox_policy( + constraint: &crate::config::Constrained, + base: SandboxPolicy, + ) -> SandboxPolicy { + let candidates = [ + SandboxPolicy::new_read_only_policy(), + SandboxPolicy::new_workspace_write_policy(), + SandboxPolicy::DangerFullAccess, + ]; + candidates + .into_iter() + .find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok()) + .unwrap_or(base) + } + let (_session, mut turn) = make_session_and_context().await; let base_instructions = BaseInstructions { text: "base".to_string(), @@ -1181,8 +1212,14 @@ mod tests { let temp_dir = tempfile::tempdir().expect("temp dir"); turn.cwd = temp_dir.path().to_path_buf(); turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo")); - turn.approval_policy = AskForApproval::Never; - turn.sandbox_policy = SandboxPolicy::DangerFullAccess; + turn.approval_policy = pick_allowed_approval_policy( + &turn.config.approval_policy, + *turn.config.approval_policy.get(), + ); + turn.sandbox_policy = pick_allowed_sandbox_policy( + &turn.config.sandbox_policy, + turn.config.sandbox_policy.get().clone(), + ); let config = build_agent_spawn_config(&base_instructions, &turn, 0).expect("spawn config"); let mut expected = (*turn.config).clone(); From 4ee039744ef5e1aa16657cb8be06cdc4dc6cdcb1 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Thu, 5 Feb 2026 18:22:30 -0800 Subject: [PATCH 89/90] feat: expose detailed metrics to runtime metrics (#10699) --- .../core/tests/suite/client_websockets.rs | 10 ++- codex-rs/otel/src/metrics/names.rs | 8 +++ codex-rs/otel/src/metrics/runtime_metrics.rs | 67 +++++++++++++++++ codex-rs/otel/src/traces/otel_manager.rs | 44 ++++++++++++ codex-rs/otel/tests/suite/runtime_summary.rs | 6 +- codex-rs/tui/src/chatwidget.rs | 71 +++++++++++++++++-- codex-rs/tui/src/chatwidget/tests.rs | 46 ++++++++++++ codex-rs/tui/src/history_cell.rs | 38 +++++++++- 8 files changed, 279 insertions(+), 11 deletions(-) diff --git a/codex-rs/core/tests/suite/client_websockets.rs b/codex-rs/core/tests/suite/client_websockets.rs index 76a1f0b3c6..bf170bb757 100644 --- a/codex-rs/core/tests/suite/client_websockets.rs +++ b/codex-rs/core/tests/suite/client_websockets.rs @@ -127,7 +127,11 @@ async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics "type": "responsesapi.websocket_timing", "timing_metrics": { "responses_duration_excl_engine_and_client_tool_time_ms": 120, - "engine_service_total_ms": 450 + "engine_service_total_ms": 450, + "engine_iapi_ttft_total_ms": 310, + "engine_service_ttft_total_ms": 340, + "engine_iapi_tbt_across_engine_calls_ms": 220, + "engine_service_tbt_across_engine_calls_ms": 260 } }), ev_completed("resp-1"), @@ -154,6 +158,10 @@ async fn responses_websocket_includes_timing_metrics_header_when_runtime_metrics .expect("runtime metrics summary"); assert_eq!(summary.responses_api_overhead_ms, 120); assert_eq!(summary.responses_api_inference_time_ms, 450); + assert_eq!(summary.responses_api_engine_iapi_ttft_ms, 310); + assert_eq!(summary.responses_api_engine_service_ttft_ms, 340); + assert_eq!(summary.responses_api_engine_iapi_tbt_ms, 220); + assert_eq!(summary.responses_api_engine_service_tbt_ms, 260); server.shutdown().await; } diff --git a/codex-rs/otel/src/metrics/names.rs b/codex-rs/otel/src/metrics/names.rs index 97c4f4e6ed..76b46d2450 100644 --- a/codex-rs/otel/src/metrics/names.rs +++ b/codex-rs/otel/src/metrics/names.rs @@ -12,3 +12,11 @@ pub(crate) const RESPONSES_API_OVERHEAD_DURATION_METRIC: &str = "codex.responses_api_overhead.duration_ms"; pub(crate) const RESPONSES_API_INFERENCE_TIME_DURATION_METRIC: &str = "codex.responses_api_inference_time.duration_ms"; +pub(crate) const RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC: &str = + "codex.responses_api_engine_iapi_ttft.duration_ms"; +pub(crate) const RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC: &str = + "codex.responses_api_engine_service_ttft.duration_ms"; +pub(crate) const RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC: &str = + "codex.responses_api_engine_iapi_tbt.duration_ms"; +pub(crate) const RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC: &str = + "codex.responses_api_engine_service_tbt.duration_ms"; diff --git a/codex-rs/otel/src/metrics/runtime_metrics.rs b/codex-rs/otel/src/metrics/runtime_metrics.rs index d59ff4b1b7..dcac367d87 100644 --- a/codex-rs/otel/src/metrics/runtime_metrics.rs +++ b/codex-rs/otel/src/metrics/runtime_metrics.rs @@ -1,5 +1,9 @@ use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC; use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; use crate::metrics::names::RESPONSES_API_OVERHEAD_DURATION_METRIC; use crate::metrics::names::SSE_EVENT_COUNT_METRIC; @@ -25,6 +29,11 @@ impl RuntimeMetricTotals { pub fn is_empty(self) -> bool { self.count == 0 && self.duration_ms == 0 } + + pub fn merge(&mut self, other: Self) { + self.count = self.count.saturating_add(other.count); + self.duration_ms = self.duration_ms.saturating_add(other.duration_ms); + } } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -36,6 +45,10 @@ pub struct RuntimeMetricsSummary { pub websocket_events: RuntimeMetricTotals, pub responses_api_overhead_ms: u64, pub responses_api_inference_time_ms: u64, + pub responses_api_engine_iapi_ttft_ms: u64, + pub responses_api_engine_service_ttft_ms: u64, + pub responses_api_engine_iapi_tbt_ms: u64, + pub responses_api_engine_service_tbt_ms: u64, } impl RuntimeMetricsSummary { @@ -47,6 +60,48 @@ impl RuntimeMetricsSummary { && self.websocket_events.is_empty() && self.responses_api_overhead_ms == 0 && self.responses_api_inference_time_ms == 0 + && self.responses_api_engine_iapi_ttft_ms == 0 + && self.responses_api_engine_service_ttft_ms == 0 + && self.responses_api_engine_iapi_tbt_ms == 0 + && self.responses_api_engine_service_tbt_ms == 0 + } + + pub fn merge(&mut self, other: Self) { + self.tool_calls.merge(other.tool_calls); + self.api_calls.merge(other.api_calls); + self.streaming_events.merge(other.streaming_events); + self.websocket_calls.merge(other.websocket_calls); + self.websocket_events.merge(other.websocket_events); + if other.responses_api_overhead_ms > 0 { + self.responses_api_overhead_ms = other.responses_api_overhead_ms; + } + if other.responses_api_inference_time_ms > 0 { + self.responses_api_inference_time_ms = other.responses_api_inference_time_ms; + } + if other.responses_api_engine_iapi_ttft_ms > 0 { + self.responses_api_engine_iapi_ttft_ms = other.responses_api_engine_iapi_ttft_ms; + } + if other.responses_api_engine_service_ttft_ms > 0 { + self.responses_api_engine_service_ttft_ms = other.responses_api_engine_service_ttft_ms; + } + if other.responses_api_engine_iapi_tbt_ms > 0 { + self.responses_api_engine_iapi_tbt_ms = other.responses_api_engine_iapi_tbt_ms; + } + if other.responses_api_engine_service_tbt_ms > 0 { + self.responses_api_engine_service_tbt_ms = other.responses_api_engine_service_tbt_ms; + } + } + + pub fn responses_api_summary(&self) -> RuntimeMetricsSummary { + Self { + responses_api_overhead_ms: self.responses_api_overhead_ms, + responses_api_inference_time_ms: self.responses_api_inference_time_ms, + responses_api_engine_iapi_ttft_ms: self.responses_api_engine_iapi_ttft_ms, + responses_api_engine_service_ttft_ms: self.responses_api_engine_service_ttft_ms, + responses_api_engine_iapi_tbt_ms: self.responses_api_engine_iapi_tbt_ms, + responses_api_engine_service_tbt_ms: self.responses_api_engine_service_tbt_ms, + ..RuntimeMetricsSummary::default() + } } pub(crate) fn from_snapshot(snapshot: &ResourceMetrics) -> Self { @@ -74,6 +129,14 @@ impl RuntimeMetricsSummary { sum_histogram_ms(snapshot, RESPONSES_API_OVERHEAD_DURATION_METRIC); let responses_api_inference_time_ms = sum_histogram_ms(snapshot, RESPONSES_API_INFERENCE_TIME_DURATION_METRIC); + let responses_api_engine_iapi_ttft_ms = + sum_histogram_ms(snapshot, RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC); + let responses_api_engine_service_ttft_ms = + sum_histogram_ms(snapshot, RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC); + let responses_api_engine_iapi_tbt_ms = + sum_histogram_ms(snapshot, RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC); + let responses_api_engine_service_tbt_ms = + sum_histogram_ms(snapshot, RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC); Self { tool_calls, api_calls, @@ -82,6 +145,10 @@ impl RuntimeMetricsSummary { websocket_events, responses_api_overhead_ms, responses_api_inference_time_ms, + responses_api_engine_iapi_ttft_ms, + responses_api_engine_service_ttft_ms, + responses_api_engine_iapi_tbt_ms, + responses_api_engine_service_tbt_ms, } } } diff --git a/codex-rs/otel/src/traces/otel_manager.rs b/codex-rs/otel/src/traces/otel_manager.rs index aa3006da01..c54c64c6cb 100644 --- a/codex-rs/otel/src/traces/otel_manager.rs +++ b/codex-rs/otel/src/traces/otel_manager.rs @@ -1,6 +1,10 @@ use crate::TelemetryAuthMode; use crate::metrics::names::API_CALL_COUNT_METRIC; use crate::metrics::names::API_CALL_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC; +use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC; use crate::metrics::names::RESPONSES_API_INFERENCE_TIME_DURATION_METRIC; use crate::metrics::names::RESPONSES_API_OVERHEAD_DURATION_METRIC; use crate::metrics::names::SSE_EVENT_COUNT_METRIC; @@ -48,6 +52,10 @@ const RESPONSES_WEBSOCKET_TIMING_KIND: &str = "responsesapi.websocket_timing"; const RESPONSES_WEBSOCKET_TIMING_METRICS_FIELD: &str = "timing_metrics"; const RESPONSES_API_OVERHEAD_FIELD: &str = "responses_duration_excl_engine_and_client_tool_time_ms"; const RESPONSES_API_INFERENCE_FIELD: &str = "engine_service_total_ms"; +const RESPONSES_API_ENGINE_IAPI_TTFT_FIELD: &str = "engine_iapi_ttft_total_ms"; +const RESPONSES_API_ENGINE_SERVICE_TTFT_FIELD: &str = "engine_service_ttft_total_ms"; +const RESPONSES_API_ENGINE_IAPI_TBT_FIELD: &str = "engine_iapi_tbt_across_engine_calls_ms"; +const RESPONSES_API_ENGINE_SERVICE_TBT_FIELD: &str = "engine_service_tbt_across_engine_calls_ms"; impl OtelManager { #[allow(clippy::too_many_arguments)] @@ -674,6 +682,42 @@ impl OtelManager { if let Some(duration) = duration_from_ms_value(inference_value) { self.record_duration(RESPONSES_API_INFERENCE_TIME_DURATION_METRIC, duration, &[]); } + + let engine_iapi_ttft_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_ENGINE_IAPI_TTFT_FIELD)); + if let Some(duration) = duration_from_ms_value(engine_iapi_ttft_value) { + self.record_duration( + RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC, + duration, + &[], + ); + } + + let engine_service_ttft_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_ENGINE_SERVICE_TTFT_FIELD)); + if let Some(duration) = duration_from_ms_value(engine_service_ttft_value) { + self.record_duration( + RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC, + duration, + &[], + ); + } + + let engine_iapi_tbt_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_ENGINE_IAPI_TBT_FIELD)); + if let Some(duration) = duration_from_ms_value(engine_iapi_tbt_value) { + self.record_duration(RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC, duration, &[]); + } + + let engine_service_tbt_value = + timing_metrics.and_then(|value| value.get(RESPONSES_API_ENGINE_SERVICE_TBT_FIELD)); + if let Some(duration) = duration_from_ms_value(engine_service_tbt_value) { + self.record_duration( + RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC, + duration, + &[], + ); + } } fn responses_type(event: &ResponseEvent) -> String { diff --git a/codex-rs/otel/tests/suite/runtime_summary.rs b/codex-rs/otel/tests/suite/runtime_summary.rs index 6ef2927f43..79c6b258e7 100644 --- a/codex-rs/otel/tests/suite/runtime_summary.rs +++ b/codex-rs/otel/tests/suite/runtime_summary.rs @@ -67,7 +67,7 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( Option>, codex_api::ApiError, > = Ok(Some(Ok(Message::Text( - r#"{"type":"responsesapi.websocket_timing","timing_metrics":{"responses_duration_excl_engine_and_client_tool_time_ms":124,"engine_service_total_ms":457}}"# + r#"{"type":"responsesapi.websocket_timing","timing_metrics":{"responses_duration_excl_engine_and_client_tool_time_ms":124,"engine_service_total_ms":457,"engine_iapi_ttft_total_ms":211,"engine_service_ttft_total_ms":233,"engine_iapi_tbt_across_engine_calls_ms":377,"engine_service_tbt_across_engine_calls_ms":399}}"# .into(), )))); manager.record_websocket_event(&ws_timing_response, Duration::from_millis(20)); @@ -98,6 +98,10 @@ fn runtime_metrics_summary_collects_tool_api_and_streaming_metrics() -> Result<( }, responses_api_overhead_ms: 124, responses_api_inference_time_ms: 457, + responses_api_engine_iapi_ttft_ms: 211, + responses_api_engine_service_ttft_ms: 233, + responses_api_engine_iapi_tbt_ms: 377, + responses_api_engine_service_tbt_ms: 399, }; assert_eq!(summary, expected); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 93d21610d3..39e757fa78 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -105,6 +105,7 @@ use codex_core::skills::model::SkillMetadata; #[cfg(target_os = "windows")] use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_otel::OtelManager; +use codex_otel::RuntimeMetricsSummary; use codex_protocol::ThreadId; use codex_protocol::account::PlanType; use codex_protocol::approvals::ElicitationRequestEvent; @@ -587,7 +588,8 @@ pub(crate) struct ChatWidget { // This lets the separator show per-chunk work time (since the previous separator) rather than // the total task-running time reported by the status indicator. last_separator_elapsed_secs: Option, - + // Runtime metrics accumulated across delta snapshots for the active turn. + turn_runtime_metrics: RuntimeMetricsSummary, last_rendered_width: std::cell::Cell>, // Feedback sink for /feedback feedback: codex_feedback::CodexFeedback, @@ -931,6 +933,32 @@ impl ChatWidget { self.request_status_line_branch(cwd); } + fn collect_runtime_metrics_delta(&mut self) { + if let Some(delta) = self.otel_manager.runtime_metrics_summary() { + self.apply_runtime_metrics_delta(delta); + } + } + + fn apply_runtime_metrics_delta(&mut self, delta: RuntimeMetricsSummary) { + let should_log_timing = has_websocket_timing_metrics(delta); + self.turn_runtime_metrics.merge(delta); + if should_log_timing { + self.log_websocket_timing_totals(delta); + } + } + + fn log_websocket_timing_totals(&mut self, delta: RuntimeMetricsSummary) { + if let Some(label) = history_cell::runtime_metrics_label(delta.responses_api_summary()) { + self.add_plain_history_lines(vec![ + vec!["• ".dim(), format!("WebSocket timing: {label}").dark_gray()].into(), + ]); + } + } + + fn refresh_runtime_metrics(&mut self) { + self.collect_runtime_metrics_delta(); + } + fn restore_retry_status_header_if_present(&mut self) { if let Some(header) = self.retry_status_header.take() { self.set_status_header(header); @@ -1218,6 +1246,7 @@ impl ChatWidget { self.plan_item_active = false; self.adaptive_chunking.reset(); self.plan_stream_controller = None; + self.turn_runtime_metrics = RuntimeMetricsSummary::default(); self.otel_manager.reset_runtime_metrics(); self.bottom_pane.clear_quit_shortcut_hint(); self.quit_shortcut_expires_at = None; @@ -1241,17 +1270,25 @@ impl ChatWidget { } self.flush_unified_exec_wait_streak(); if !from_replay { - let runtime_metrics = self.otel_manager.runtime_metrics_summary(); - if runtime_metrics.is_some() { - let elapsed_seconds = self - .bottom_pane - .status_widget() - .map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds); + self.collect_runtime_metrics_delta(); + let runtime_metrics = + (!self.turn_runtime_metrics.is_empty()).then_some(self.turn_runtime_metrics); + let show_work_separator = self.needs_final_message_separator && self.had_work_activity; + if show_work_separator || runtime_metrics.is_some() { + let elapsed_seconds = if show_work_separator { + self.bottom_pane + .status_widget() + .map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds) + .map(|current| self.worked_elapsed_from(current)) + } else { + None + }; self.add_to_history(history_cell::FinalMessageSeparator::new( elapsed_seconds, runtime_metrics, )); } + self.turn_runtime_metrics = RuntimeMetricsSummary::default(); self.needs_final_message_separator = false; self.had_work_activity = false; self.request_status_line_branch_refresh(); @@ -2087,6 +2124,10 @@ impl ChatWidget { } self.app_event_tx.send(AppEvent::StopCommitAnimation); } + + if self.agent_turn_running { + self.refresh_runtime_metrics(); + } } fn flush_interrupt_queue(&mut self) { @@ -2529,6 +2570,7 @@ impl ChatWidget { plan_delta_buffer: String::new(), plan_item_active: false, last_separator_elapsed_secs: None, + turn_runtime_metrics: RuntimeMetricsSummary::default(), last_rendered_width: std::cell::Cell::new(None), feedback, feedback_audience, @@ -2690,6 +2732,7 @@ impl ChatWidget { needs_final_message_separator: false, had_work_activity: false, last_separator_elapsed_secs: None, + turn_runtime_metrics: RuntimeMetricsSummary::default(), last_rendered_width: std::cell::Cell::new(None), feedback, feedback_audience, @@ -2840,6 +2883,7 @@ impl ChatWidget { plan_delta_buffer: String::new(), plan_item_active: false, last_separator_elapsed_secs: None, + turn_runtime_metrics: RuntimeMetricsSummary::default(), last_rendered_width: std::cell::Cell::new(None), feedback, feedback_audience, @@ -3859,6 +3903,10 @@ impl ChatWidget { } } } + + if !from_replay && self.agent_turn_running { + self.refresh_runtime_metrics(); + } } fn on_entered_review_mode(&mut self, review: ReviewRequest, from_replay: bool) { @@ -6690,6 +6738,15 @@ impl ChatWidget { } } +fn has_websocket_timing_metrics(summary: RuntimeMetricsSummary) -> bool { + summary.responses_api_overhead_ms > 0 + || summary.responses_api_inference_time_ms > 0 + || summary.responses_api_engine_iapi_ttft_ms > 0 + || summary.responses_api_engine_service_ttft_ms > 0 + || summary.responses_api_engine_iapi_tbt_ms > 0 + || summary.responses_api_engine_service_tbt_ms > 0 +} + impl Drop for ChatWidget { fn drop(&mut self) { self.stop_rate_limit_poller(); diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index f49493b6a1..1b70abf975 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -62,6 +62,7 @@ use codex_core::protocol::UndoStartedEvent; use codex_core::protocol::ViewImageToolCallEvent; use codex_core::protocol::WarningEvent; use codex_otel::OtelManager; +use codex_otel::RuntimeMetricsSummary; use codex_protocol::ThreadId; use codex_protocol::account::PlanType; use codex_protocol::config_types::CollaborationMode; @@ -955,6 +956,7 @@ async fn make_chatwidget_manual( plan_delta_buffer: String::new(), plan_item_active: false, last_separator_elapsed_secs: None, + turn_runtime_metrics: RuntimeMetricsSummary::default(), last_rendered_width: std::cell::Cell::new(None), feedback: codex_feedback::CodexFeedback::new(), feedback_audience: FeedbackAudience::External, @@ -5029,6 +5031,50 @@ async fn stream_recovery_restores_previous_status_header() { assert!(chat.retry_status_header.is_none()); } +#[tokio::test] +async fn runtime_metrics_websocket_timing_logs_and_final_separator_sums_totals() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + chat.set_feature_enabled(Feature::RuntimeMetrics, true); + + chat.on_task_started(); + chat.apply_runtime_metrics_delta(RuntimeMetricsSummary { + responses_api_engine_iapi_ttft_ms: 120, + responses_api_engine_service_tbt_ms: 50, + ..RuntimeMetricsSummary::default() + }); + + let first_log = drain_insert_history(&mut rx) + .iter() + .map(|lines| lines_to_single_string(lines)) + .find(|line| line.contains("WebSocket timing:")) + .expect("expected websocket timing log"); + assert!(first_log.contains("TTFT: 120ms (iapi)")); + assert!(first_log.contains("TBT: 50ms (service)")); + + chat.apply_runtime_metrics_delta(RuntimeMetricsSummary { + responses_api_engine_iapi_ttft_ms: 80, + ..RuntimeMetricsSummary::default() + }); + + let second_log = drain_insert_history(&mut rx) + .iter() + .map(|lines| lines_to_single_string(lines)) + .find(|line| line.contains("WebSocket timing:")) + .expect("expected websocket timing log"); + assert!(second_log.contains("TTFT: 80ms (iapi)")); + + chat.on_task_complete(None, false); + let mut final_separator = None; + while let Ok(event) = rx.try_recv() { + if let AppEvent::InsertHistoryCell(cell) = event { + final_separator = Some(lines_to_single_string(&cell.display_lines(300))); + } + } + let final_separator = final_separator.expect("expected final separator with runtime metrics"); + assert!(final_separator.contains("TTFT: 80ms (iapi)")); + assert!(final_separator.contains("TBT: 50ms (service)")); +} + #[tokio::test] async fn multiple_agent_messages_in_single_turn_emit_multiple_headers() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index b85ca8f6bb..26a91dc6e6 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -2172,7 +2172,7 @@ impl HistoryCell for FinalMessageSeparator { } } -fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { +pub(crate) fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { let mut parts = Vec::new(); if summary.tool_calls.count > 0 { let duration = format_duration_ms(summary.tool_calls.duration_ms); @@ -2221,6 +2221,34 @@ fn runtime_metrics_label(summary: RuntimeMetricsSummary) -> Option { let duration = format_duration_ms(summary.responses_api_inference_time_ms); parts.push(format!("Responses API inference: {duration}")); } + if summary.responses_api_engine_iapi_ttft_ms > 0 + || summary.responses_api_engine_service_ttft_ms > 0 + { + let mut ttft_parts = Vec::new(); + if summary.responses_api_engine_iapi_ttft_ms > 0 { + let duration = format_duration_ms(summary.responses_api_engine_iapi_ttft_ms); + ttft_parts.push(format!("{duration} (iapi)")); + } + if summary.responses_api_engine_service_ttft_ms > 0 { + let duration = format_duration_ms(summary.responses_api_engine_service_ttft_ms); + ttft_parts.push(format!("{duration} (service)")); + } + parts.push(format!("TTFT: {}", ttft_parts.join(" "))); + } + if summary.responses_api_engine_iapi_tbt_ms > 0 + || summary.responses_api_engine_service_tbt_ms > 0 + { + let mut tbt_parts = Vec::new(); + if summary.responses_api_engine_iapi_tbt_ms > 0 { + let duration = format_duration_ms(summary.responses_api_engine_iapi_tbt_ms); + tbt_parts.push(format!("{duration} (iapi)")); + } + if summary.responses_api_engine_service_tbt_ms > 0 { + let duration = format_duration_ms(summary.responses_api_engine_service_tbt_ms); + tbt_parts.push(format!("{duration} (service)")); + } + parts.push(format!("TBT: {}", tbt_parts.join(" "))); + } if parts.is_empty() { None } else { @@ -2391,9 +2419,13 @@ mod tests { }, responses_api_overhead_ms: 650, responses_api_inference_time_ms: 1_940, + responses_api_engine_iapi_ttft_ms: 410, + responses_api_engine_service_ttft_ms: 460, + responses_api_engine_iapi_tbt_ms: 1_180, + responses_api_engine_service_tbt_ms: 1_240, }; let cell = FinalMessageSeparator::new(Some(12), Some(summary)); - let rendered = render_lines(&cell.display_lines(300)); + let rendered = render_lines(&cell.display_lines(600)); assert_eq!(rendered.len(), 1); assert!(!rendered[0].contains("Worked for")); @@ -2404,6 +2436,8 @@ mod tests { assert!(rendered[0].contains("4 events received (1.2s)")); assert!(rendered[0].contains("Responses API overhead: 650ms")); assert!(rendered[0].contains("Responses API inference: 1.9s")); + assert!(rendered[0].contains("TTFT: 410ms (iapi) 460ms (service)")); + assert!(rendered[0].contains("TBT: 1.2s (iapi) 1.2s (service)")); } #[test] From 048e0f3888a5c5bef8d2272e44b30a1aea4c8f92 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 5 Feb 2026 19:18:08 -0800 Subject: [PATCH 90/90] Gate app tooltips to macOS (#10784) - Gate app promo tips to macOS and use non-app copy elsewhere. --- codex-rs/tui/src/tooltips.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/codex-rs/tui/src/tooltips.rs b/codex-rs/tui/src/tooltips.rs index 7365c16496..a9dbd82e2f 100644 --- a/codex-rs/tui/src/tooltips.rs +++ b/codex-rs/tui/src/tooltips.rs @@ -6,9 +6,13 @@ use rand::Rng; const ANNOUNCEMENT_TIP_URL: &str = "https://raw.githubusercontent.com/openai/codex/main/announcement_tip.toml"; +const IS_MACOS: bool = cfg!(target_os = "macos"); + 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 PAID_TOOLTIP_NON_MAC: &str = "*New* 2x rate limits until *April 2nd*."; const OTHER_TOOLTIP: &str = "*New* Build faster with the **Codex App**. Run 'codex app' or visit https://chatgpt.com/codex"; +const OTHER_TOOLTIP_NON_MAC: &str = "*New* Build faster with Codex."; const FREE_GO_TOOLTIP: &str = "*New* Codex is included in your plan for free through *March 2nd* – let’s build together."; @@ -18,7 +22,15 @@ lazy_static! { static ref TOOLTIPS: Vec<&'static str> = RAW_TOOLTIPS .lines() .map(str::trim) - .filter(|line| !line.is_empty() && !line.starts_with('#')) + .filter(|line| { + if line.is_empty() || line.starts_with('#') { + return false; + } + if !IS_MACOS && line.contains("codex app") { + return false; + } + true + }) .collect(); static ref ALL_TOOLTIPS: Vec<&'static str> = { let mut tips = Vec::new(); @@ -51,12 +63,24 @@ pub(crate) fn get_tooltip(plan: Option) -> Option { | Some(PlanType::Team) | Some(PlanType::Enterprise) | Some(PlanType::Pro) => { - return Some(PAID_TOOLTIP.to_string()); + let tooltip = if IS_MACOS { + PAID_TOOLTIP + } else { + PAID_TOOLTIP_NON_MAC + }; + return Some(tooltip.to_string()); } Some(PlanType::Go) | Some(PlanType::Free) => { return Some(FREE_GO_TOOLTIP.to_string()); } - _ => return Some(OTHER_TOOLTIP.to_string()), + _ => { + let tooltip = if IS_MACOS { + OTHER_TOOLTIP + } else { + OTHER_TOOLTIP_NON_MAC + }; + return Some(tooltip.to_string()); + } } }