use std::{fmt, path::PathBuf, sync::Arc};
use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{ContentBlock, Error, Plan, SessionId, ToolCall, ToolCallUpdate};
pub trait Client {
fn request_permission(
&self,
args: RequestPermissionRequest,
) -> impl Future<Output = Result<RequestPermissionResponse, Error>>;
fn write_text_file(
&self,
args: WriteTextFileRequest,
) -> impl Future<Output = Result<(), Error>>;
fn read_text_file(
&self,
args: ReadTextFileRequest,
) -> impl Future<Output = Result<ReadTextFileResponse, Error>>;
fn session_notification(
&self,
args: SessionNotification,
) -> impl Future<Output = Result<(), Error>>;
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(extend("x-side" = "client", "x-method" = "session/update"))]
#[serde(rename_all = "camelCase")]
pub struct SessionNotification {
pub session_id: SessionId,
pub update: SessionUpdate,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case", tag = "sessionUpdate")]
pub enum SessionUpdate {
UserMessageChunk { content: ContentBlock },
AgentMessageChunk { content: ContentBlock },
AgentThoughtChunk { content: ContentBlock },
ToolCall(ToolCall),
ToolCallUpdate(ToolCallUpdate),
Plan(Plan),
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(extend("x-side" = "client", "x-method" = "session/request_permission"))]
#[serde(rename_all = "camelCase")]
pub struct RequestPermissionRequest {
pub session_id: SessionId,
pub tool_call: ToolCallUpdate,
pub options: Vec<PermissionOption>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct PermissionOption {
#[serde(rename = "optionId")]
pub id: PermissionOptionId,
pub name: String,
pub kind: PermissionOptionKind,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct PermissionOptionId(pub Arc<str>);
impl fmt::Display for PermissionOptionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PermissionOptionKind {
AllowOnce,
AllowAlways,
RejectOnce,
RejectAlways,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(extend("x-side" = "client", "x-method" = "session/request_permission"))]
#[serde(rename_all = "camelCase")]
pub struct RequestPermissionResponse {
pub outcome: RequestPermissionOutcome,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum RequestPermissionOutcome {
Cancelled,
#[serde(rename_all = "camelCase")]
Selected {
option_id: PermissionOptionId,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(extend("x-side" = "client", "x-method" = "fs/write_text_file"))]
#[serde(rename_all = "camelCase")]
pub struct WriteTextFileRequest {
pub session_id: SessionId,
pub path: PathBuf,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[schemars(extend("x-side" = "client", "x-method" = "fs/read_text_file"))]
#[serde(rename_all = "camelCase")]
pub struct ReadTextFileRequest {
pub session_id: SessionId,
pub path: PathBuf,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ReadTextFileResponse {
pub content: String,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ClientCapabilities {
#[serde(default)]
pub fs: FileSystemCapability,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileSystemCapability {
#[serde(default)]
pub read_text_file: bool,
#[serde(default)]
pub write_text_file: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
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 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,
};
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";
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
#[schemars(extend("x-docs-ignore" = true))]
pub enum AgentRequest {
WriteTextFileRequest(WriteTextFileRequest),
ReadTextFileRequest(ReadTextFileRequest),
RequestPermissionRequest(RequestPermissionRequest),
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
#[schemars(extend("x-docs-ignore" = true))]
pub enum ClientResponse {
WriteTextFileResponse,
ReadTextFileResponse(ReadTextFileResponse),
RequestPermissionResponse(RequestPermissionResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
#[schemars(extend("x-docs-ignore" = true))]
pub enum AgentNotification {
SessionNotification(SessionNotification),
}