use std::{path::PathBuf, sync::Arc};
use derive_more::{Display, From};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable_elicitation")]
use crate::elicitation::{
CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
ElicitationCapabilities,
};
use crate::{
ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, Plan,
SessionConfigOption, SessionId, SessionModeId, ToolCall, ToolCallUpdate,
};
use crate::{IntoMaybeUndefined, MaybeUndefined};
#[cfg(feature = "unstable_nes")]
use crate::{ClientNesCapabilities, PositionEncodingKind};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SessionNotification {
pub session_id: SessionId,
pub update: SessionUpdate,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl SessionNotification {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, update: SessionUpdate) -> Self {
Self {
session_id: session_id.into(),
update,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "sessionUpdate", rename_all = "snake_case")]
#[schemars(extend("discriminator" = {"propertyName": "sessionUpdate"}))]
#[non_exhaustive]
pub enum SessionUpdate {
UserMessageChunk(ContentChunk),
AgentMessageChunk(ContentChunk),
AgentThoughtChunk(ContentChunk),
ToolCall(ToolCall),
ToolCallUpdate(ToolCallUpdate),
Plan(Plan),
AvailableCommandsUpdate(AvailableCommandsUpdate),
CurrentModeUpdate(CurrentModeUpdate),
ConfigOptionUpdate(ConfigOptionUpdate),
SessionInfoUpdate(SessionInfoUpdate),
#[cfg(feature = "unstable_session_usage")]
UsageUpdate(UsageUpdate),
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct CurrentModeUpdate {
pub current_mode_id: SessionModeId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl CurrentModeUpdate {
#[must_use]
pub fn new(current_mode_id: impl Into<SessionModeId>) -> Self {
Self {
current_mode_id: current_mode_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ConfigOptionUpdate {
pub config_options: Vec<SessionConfigOption>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ConfigOptionUpdate {
#[must_use]
pub fn new(config_options: Vec<SessionConfigOption>) -> Self {
Self {
config_options,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SessionInfoUpdate {
#[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
pub title: MaybeUndefined<String>,
#[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
pub updated_at: MaybeUndefined<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl SessionInfoUpdate {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn title(mut self, title: impl IntoMaybeUndefined<String>) -> Self {
self.title = title.into_maybe_undefined();
self
}
#[must_use]
pub fn updated_at(mut self, updated_at: impl IntoMaybeUndefined<String>) -> Self {
self.updated_at = updated_at.into_maybe_undefined();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[cfg(feature = "unstable_session_usage")]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct UsageUpdate {
pub used: u64,
pub size: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost: Option<Cost>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
#[cfg(feature = "unstable_session_usage")]
impl UsageUpdate {
#[must_use]
pub fn new(used: u64, size: u64) -> Self {
Self {
used,
size,
cost: None,
meta: None,
}
}
#[must_use]
pub fn cost(mut self, cost: impl IntoOption<Cost>) -> Self {
self.cost = cost.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[cfg(feature = "unstable_session_usage")]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Cost {
pub amount: f64,
pub currency: String,
}
#[cfg(feature = "unstable_session_usage")]
impl Cost {
#[must_use]
pub fn new(amount: f64, currency: impl Into<String>) -> Self {
Self {
amount,
currency: currency.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ContentChunk {
pub content: ContentBlock,
#[cfg(feature = "unstable_message_id")]
#[serde(skip_serializing_if = "Option::is_none")]
pub message_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ContentChunk {
#[must_use]
pub fn new(content: ContentBlock) -> Self {
Self {
content,
#[cfg(feature = "unstable_message_id")]
message_id: None,
meta: None,
}
}
#[cfg(feature = "unstable_message_id")]
#[must_use]
pub fn message_id(mut self, message_id: impl IntoOption<String>) -> Self {
self.message_id = message_id.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct AvailableCommandsUpdate {
pub available_commands: Vec<AvailableCommand>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl AvailableCommandsUpdate {
#[must_use]
pub fn new(available_commands: Vec<AvailableCommand>) -> Self {
Self {
available_commands,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct AvailableCommand {
pub name: String,
pub description: String,
pub input: Option<AvailableCommandInput>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl AvailableCommand {
#[must_use]
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
input: None,
meta: None,
}
}
#[must_use]
pub fn input(mut self, input: impl IntoOption<AvailableCommandInput>) -> Self {
self.input = input.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(untagged, rename_all = "camelCase")]
#[non_exhaustive]
pub enum AvailableCommandInput {
Unstructured(UnstructuredCommandInput),
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct UnstructuredCommandInput {
pub hint: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl UnstructuredCommandInput {
#[must_use]
pub fn new(hint: impl Into<String>) -> Self {
Self {
hint: hint.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct RequestPermissionRequest {
pub session_id: SessionId,
pub tool_call: ToolCallUpdate,
pub options: Vec<PermissionOption>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl RequestPermissionRequest {
#[must_use]
pub fn new(
session_id: impl Into<SessionId>,
tool_call: ToolCallUpdate,
options: Vec<PermissionOption>,
) -> Self {
Self {
session_id: session_id.into(),
tool_call,
options,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct PermissionOption {
pub option_id: PermissionOptionId,
pub name: String,
pub kind: PermissionOptionKind,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl PermissionOption {
#[must_use]
pub fn new(
option_id: impl Into<PermissionOptionId>,
name: impl Into<String>,
kind: PermissionOptionKind,
) -> Self {
Self {
option_id: option_id.into(),
name: name.into(),
kind,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
#[serde(transparent)]
#[from(Arc<str>, String, &'static str)]
#[non_exhaustive]
pub struct PermissionOptionId(pub Arc<str>);
impl PermissionOptionId {
#[must_use]
pub fn new(id: impl Into<Arc<str>>) -> Self {
Self(id.into())
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PermissionOptionKind {
AllowOnce,
AllowAlways,
RejectOnce,
RejectAlways,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct RequestPermissionResponse {
pub outcome: RequestPermissionOutcome,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl RequestPermissionResponse {
#[must_use]
pub fn new(outcome: RequestPermissionOutcome) -> Self {
Self {
outcome,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(tag = "outcome", rename_all = "snake_case")]
#[schemars(extend("discriminator" = {"propertyName": "outcome"}))]
#[non_exhaustive]
pub enum RequestPermissionOutcome {
Cancelled,
#[serde(rename_all = "camelCase")]
Selected(SelectedPermissionOutcome),
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct SelectedPermissionOutcome {
pub option_id: PermissionOptionId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl SelectedPermissionOutcome {
#[must_use]
pub fn new(option_id: impl Into<PermissionOptionId>) -> Self {
Self {
option_id: option_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct WriteTextFileRequest {
pub session_id: SessionId,
pub path: PathBuf,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl WriteTextFileRequest {
#[must_use]
pub fn new(
session_id: impl Into<SessionId>,
path: impl Into<PathBuf>,
content: impl Into<String>,
) -> Self {
Self {
session_id: session_id.into(),
path: path.into(),
content: content.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))]
#[non_exhaustive]
pub struct WriteTextFileResponse {
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl WriteTextFileResponse {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ReadTextFileRequest {
pub session_id: SessionId,
pub path: PathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
pub line: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ReadTextFileRequest {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, path: impl Into<PathBuf>) -> Self {
Self {
session_id: session_id.into(),
path: path.into(),
line: None,
limit: None,
meta: None,
}
}
#[must_use]
pub fn line(mut self, line: impl IntoOption<u32>) -> Self {
self.line = line.into_option();
self
}
#[must_use]
pub fn limit(mut self, limit: impl IntoOption<u32>) -> Self {
self.limit = limit.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ReadTextFileResponse {
pub content: String,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ReadTextFileResponse {
#[must_use]
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash, Display, From)]
#[serde(transparent)]
#[from(Arc<str>, String, &'static str)]
#[non_exhaustive]
pub struct TerminalId(pub Arc<str>);
impl TerminalId {
#[must_use]
pub fn new(id: impl Into<Arc<str>>) -> Self {
Self(id.into())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
#[non_exhaustive]
pub struct CreateTerminalRequest {
pub session_id: SessionId,
pub command: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub env: Vec<crate::EnvVariable>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cwd: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_byte_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl CreateTerminalRequest {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, command: impl Into<String>) -> Self {
Self {
session_id: session_id.into(),
command: command.into(),
args: Vec::new(),
env: Vec::new(),
cwd: None,
output_byte_limit: None,
meta: None,
}
}
#[must_use]
pub fn args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
#[must_use]
pub fn env(mut self, env: Vec<crate::EnvVariable>) -> Self {
self.env = env;
self
}
#[must_use]
pub fn cwd(mut self, cwd: impl IntoOption<PathBuf>) -> Self {
self.cwd = cwd.into_option();
self
}
#[must_use]
pub fn output_byte_limit(mut self, output_byte_limit: impl IntoOption<u64>) -> Self {
self.output_byte_limit = output_byte_limit.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_CREATE_METHOD_NAME))]
#[non_exhaustive]
pub struct CreateTerminalResponse {
pub terminal_id: TerminalId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl CreateTerminalResponse {
#[must_use]
pub fn new(terminal_id: impl Into<TerminalId>) -> Self {
Self {
terminal_id: terminal_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
#[non_exhaustive]
pub struct TerminalOutputRequest {
pub session_id: SessionId,
pub terminal_id: TerminalId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl TerminalOutputRequest {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
Self {
session_id: session_id.into(),
terminal_id: terminal_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_OUTPUT_METHOD_NAME))]
#[non_exhaustive]
pub struct TerminalOutputResponse {
pub output: String,
pub truncated: bool,
pub exit_status: Option<TerminalExitStatus>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl TerminalOutputResponse {
#[must_use]
pub fn new(output: impl Into<String>, truncated: bool) -> Self {
Self {
output: output.into(),
truncated,
exit_status: None,
meta: None,
}
}
#[must_use]
pub fn exit_status(mut self, exit_status: impl IntoOption<TerminalExitStatus>) -> Self {
self.exit_status = exit_status.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
#[non_exhaustive]
pub struct ReleaseTerminalRequest {
pub session_id: SessionId,
pub terminal_id: TerminalId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ReleaseTerminalRequest {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
Self {
session_id: session_id.into(),
terminal_id: terminal_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))]
#[non_exhaustive]
pub struct ReleaseTerminalResponse {
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ReleaseTerminalResponse {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
#[non_exhaustive]
pub struct KillTerminalRequest {
pub session_id: SessionId,
pub terminal_id: TerminalId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl KillTerminalRequest {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
Self {
session_id: session_id.into(),
terminal_id: terminal_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))]
#[non_exhaustive]
pub struct KillTerminalResponse {
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl KillTerminalResponse {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
#[non_exhaustive]
pub struct WaitForTerminalExitRequest {
pub session_id: SessionId,
pub terminal_id: TerminalId,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl WaitForTerminalExitRequest {
#[must_use]
pub fn new(session_id: impl Into<SessionId>, terminal_id: impl Into<TerminalId>) -> Self {
Self {
session_id: session_id.into(),
terminal_id: terminal_id.into(),
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_WAIT_FOR_EXIT_METHOD_NAME))]
#[non_exhaustive]
pub struct WaitForTerminalExitResponse {
#[serde(flatten)]
pub exit_status: TerminalExitStatus,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl WaitForTerminalExitResponse {
#[must_use]
pub fn new(exit_status: TerminalExitStatus) -> Self {
Self {
exit_status,
meta: None,
}
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct TerminalExitStatus {
pub exit_code: Option<u32>,
pub signal: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl TerminalExitStatus {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn exit_code(mut self, exit_code: impl IntoOption<u32>) -> Self {
self.exit_code = exit_code.into_option();
self
}
#[must_use]
pub fn signal(mut self, signal: impl IntoOption<String>) -> Self {
self.signal = signal.into_option();
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ClientCapabilities {
#[serde(default)]
pub fs: FileSystemCapabilities,
#[serde(default)]
pub terminal: bool,
#[cfg(feature = "unstable_auth_methods")]
#[serde(default)]
pub auth: AuthCapabilities,
#[cfg(feature = "unstable_elicitation")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub elicitation: Option<ElicitationCapabilities>,
#[cfg(feature = "unstable_nes")]
#[serde(skip_serializing_if = "Option::is_none")]
pub nes: Option<ClientNesCapabilities>,
#[cfg(feature = "unstable_nes")]
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub position_encodings: Vec<PositionEncodingKind>,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl ClientCapabilities {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn fs(mut self, fs: FileSystemCapabilities) -> Self {
self.fs = fs;
self
}
#[must_use]
pub fn terminal(mut self, terminal: bool) -> Self {
self.terminal = terminal;
self
}
#[cfg(feature = "unstable_auth_methods")]
#[must_use]
pub fn auth(mut self, auth: AuthCapabilities) -> Self {
self.auth = auth;
self
}
#[cfg(feature = "unstable_elicitation")]
#[must_use]
pub fn elicitation(mut self, elicitation: impl IntoOption<ElicitationCapabilities>) -> Self {
self.elicitation = elicitation.into_option();
self
}
#[cfg(feature = "unstable_nes")]
#[must_use]
pub fn nes(mut self, nes: impl IntoOption<ClientNesCapabilities>) -> Self {
self.nes = nes.into_option();
self
}
#[cfg(feature = "unstable_nes")]
#[must_use]
pub fn position_encodings(mut self, position_encodings: Vec<PositionEncodingKind>) -> Self {
self.position_encodings = position_encodings;
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[cfg(feature = "unstable_auth_methods")]
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct AuthCapabilities {
#[serde(default)]
pub terminal: bool,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
#[cfg(feature = "unstable_auth_methods")]
impl AuthCapabilities {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn terminal(mut self, terminal: bool) -> Self {
self.terminal = terminal;
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct FileSystemCapabilities {
#[serde(default)]
pub read_text_file: bool,
#[serde(default)]
pub write_text_file: bool,
#[serde(skip_serializing_if = "Option::is_none", rename = "_meta")]
pub meta: Option<Meta>,
}
impl FileSystemCapabilities {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn read_text_file(mut self, read_text_file: bool) -> Self {
self.read_text_file = read_text_file;
self
}
#[must_use]
pub fn write_text_file(mut self, write_text_file: bool) -> Self {
self.write_text_file = write_text_file;
self
}
#[must_use]
pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
self.meta = meta.into_option();
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct ClientMethodNames {
pub session_request_permission: &'static str,
pub session_update: &'static str,
pub fs_write_text_file: &'static str,
pub fs_read_text_file: &'static str,
pub terminal_create: &'static str,
pub terminal_output: &'static str,
pub terminal_release: &'static str,
pub terminal_wait_for_exit: &'static str,
pub terminal_kill: &'static str,
#[cfg(feature = "unstable_elicitation")]
pub elicitation_create: &'static str,
#[cfg(feature = "unstable_elicitation")]
pub elicitation_complete: &'static str,
}
pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
session_update: SESSION_UPDATE_NOTIFICATION,
session_request_permission: SESSION_REQUEST_PERMISSION_METHOD_NAME,
fs_write_text_file: FS_WRITE_TEXT_FILE_METHOD_NAME,
fs_read_text_file: FS_READ_TEXT_FILE_METHOD_NAME,
terminal_create: TERMINAL_CREATE_METHOD_NAME,
terminal_output: TERMINAL_OUTPUT_METHOD_NAME,
terminal_release: TERMINAL_RELEASE_METHOD_NAME,
terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
terminal_kill: TERMINAL_KILL_METHOD_NAME,
#[cfg(feature = "unstable_elicitation")]
elicitation_create: ELICITATION_CREATE_METHOD_NAME,
#[cfg(feature = "unstable_elicitation")]
elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
};
pub(crate) const SESSION_UPDATE_NOTIFICATION: &str = "session/update";
pub(crate) const SESSION_REQUEST_PERMISSION_METHOD_NAME: &str = "session/request_permission";
pub(crate) const FS_WRITE_TEXT_FILE_METHOD_NAME: &str = "fs/write_text_file";
pub(crate) const FS_READ_TEXT_FILE_METHOD_NAME: &str = "fs/read_text_file";
pub(crate) const TERMINAL_CREATE_METHOD_NAME: &str = "terminal/create";
pub(crate) const TERMINAL_OUTPUT_METHOD_NAME: &str = "terminal/output";
pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
#[cfg(feature = "unstable_elicitation")]
pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
#[cfg(feature = "unstable_elicitation")]
pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
#[schemars(inline)]
#[non_exhaustive]
pub enum AgentRequest {
WriteTextFileRequest(WriteTextFileRequest),
ReadTextFileRequest(ReadTextFileRequest),
RequestPermissionRequest(RequestPermissionRequest),
CreateTerminalRequest(CreateTerminalRequest),
TerminalOutputRequest(TerminalOutputRequest),
ReleaseTerminalRequest(ReleaseTerminalRequest),
WaitForTerminalExitRequest(WaitForTerminalExitRequest),
KillTerminalRequest(KillTerminalRequest),
#[cfg(feature = "unstable_elicitation")]
CreateElicitationRequest(CreateElicitationRequest),
ExtMethodRequest(ExtRequest),
}
impl AgentRequest {
#[must_use]
pub fn method(&self) -> &str {
match self {
Self::WriteTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_write_text_file,
Self::ReadTextFileRequest(_) => CLIENT_METHOD_NAMES.fs_read_text_file,
Self::RequestPermissionRequest(_) => CLIENT_METHOD_NAMES.session_request_permission,
Self::CreateTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_create,
Self::TerminalOutputRequest(_) => CLIENT_METHOD_NAMES.terminal_output,
Self::ReleaseTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_release,
Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
#[cfg(feature = "unstable_elicitation")]
Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
Self::ExtMethodRequest(ext_request) => &ext_request.method,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
#[schemars(inline)]
#[non_exhaustive]
pub enum ClientResponse {
WriteTextFileResponse(#[serde(default)] WriteTextFileResponse),
ReadTextFileResponse(ReadTextFileResponse),
RequestPermissionResponse(RequestPermissionResponse),
CreateTerminalResponse(CreateTerminalResponse),
TerminalOutputResponse(TerminalOutputResponse),
ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse),
WaitForTerminalExitResponse(WaitForTerminalExitResponse),
KillTerminalResponse(#[serde(default)] KillTerminalResponse),
#[cfg(feature = "unstable_elicitation")]
CreateElicitationResponse(CreateElicitationResponse),
ExtMethodResponse(ExtResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
#[expect(clippy::large_enum_variant)]
#[schemars(inline)]
#[non_exhaustive]
pub enum AgentNotification {
SessionNotification(SessionNotification),
#[cfg(feature = "unstable_elicitation")]
CompleteElicitationNotification(CompleteElicitationNotification),
ExtNotification(ExtNotification),
}
impl AgentNotification {
#[must_use]
pub fn method(&self) -> &str {
match self {
Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
#[cfg(feature = "unstable_elicitation")]
Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
Self::ExtNotification(ext_notification) => &ext_notification.method,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialization_behavior() {
use serde_json::json;
assert_eq!(
serde_json::from_value::<SessionInfoUpdate>(json!({})).unwrap(),
SessionInfoUpdate {
title: MaybeUndefined::Undefined,
updated_at: MaybeUndefined::Undefined,
meta: None
}
);
assert_eq!(
serde_json::from_value::<SessionInfoUpdate>(json!({"title": null, "updatedAt": null}))
.unwrap(),
SessionInfoUpdate {
title: MaybeUndefined::Null,
updated_at: MaybeUndefined::Null,
meta: None
}
);
assert_eq!(
serde_json::from_value::<SessionInfoUpdate>(
json!({"title": "title", "updatedAt": "timestamp"})
)
.unwrap(),
SessionInfoUpdate {
title: MaybeUndefined::Value("title".to_string()),
updated_at: MaybeUndefined::Value("timestamp".to_string()),
meta: None
}
);
assert_eq!(
serde_json::to_value(SessionInfoUpdate::new()).unwrap(),
json!({})
);
assert_eq!(
serde_json::to_value(SessionInfoUpdate::new().title("title")).unwrap(),
json!({"title": "title"})
);
assert_eq!(
serde_json::to_value(SessionInfoUpdate::new().title(None)).unwrap(),
json!({"title": null})
);
assert_eq!(
serde_json::to_value(
SessionInfoUpdate::new()
.title("title")
.title(MaybeUndefined::Undefined)
)
.unwrap(),
json!({})
);
}
#[cfg(feature = "unstable_nes")]
#[test]
fn test_client_capabilities_position_encodings_serialization() {
use serde_json::json;
let capabilities = ClientCapabilities::new().position_encodings(vec![
PositionEncodingKind::Utf32,
PositionEncodingKind::Utf16,
]);
let json = serde_json::to_value(&capabilities).unwrap();
assert_eq!(json["positionEncodings"], json!(["utf-32", "utf-16"]));
}
}