use std::fmt;
use futures::stream::BoxStream;
use serde::{Deserialize, Serialize};
use tokio_util::sync::CancellationToken;
use crate::json::RawJson;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Delivery {
Steer,
FollowUp,
NextTurn,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Provenance {
pub source: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub synopsis: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub severity: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendOptions {
pub trigger_turn: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deliver_as: Option<Delivery>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<Provenance>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserMessageOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deliver_as: Option<Delivery>,
pub expand_prompt_templates: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<Provenance>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SessionsScope {
Cwd,
All,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "kebab-case")]
pub enum SessionQuery {
Header,
SessionId,
SessionFile,
LeafId,
Entry {
id: String,
},
Entries,
Branch {
from: Option<String>,
},
ContextEntries,
Projection,
Tree,
Label {
id: String,
},
SessionName,
}
#[derive(Clone, Debug, PartialEq)]
pub enum SessionWrite {
SetSessionName(Option<String>),
SetLabel {
id: String,
label: Option<String>,
},
AppendEntry {
custom_type: String,
data: Option<RawJson>,
},
SendMessage {
message: RawJson,
options: SendOptions,
},
SendUserMessage {
content: RawJson,
options: UserMessageOptions,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SessionsIndexQuery {
List(SessionsScope),
Read {
path: String,
},
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ControlOp {
Model,
RoutedModel,
SetModel {
provider: String,
id: String,
},
SetDefaultModel {
provider: String,
id: String,
thinking: Option<String>,
},
ThinkingLevel,
SetThinkingLevel(String),
ModelsList,
ScopedModels,
RunState,
IsIdle,
HasPendingMessages,
ContextUsage,
SessionStats,
SystemPrompt,
IsProjectTrusted,
GetSettings,
Abort,
Compact {
custom_instructions: Option<String>,
},
Shutdown,
SystemPromptOptions,
WaitForIdle,
NewSession {
parent_session: Option<String>,
},
Fork {
entry_id: String,
at: bool,
},
SwitchSession {
path: String,
},
NavigateTree {
target_id: String,
options: RawJson,
},
Reload,
ActiveTools,
SetActiveTools(Vec<String>),
AllTools,
CommandsList,
CommandInvoke {
name: String,
args: String,
},
ExecuteTool {
name: String,
args: RawJson,
cancel: Option<CancellationToken>,
},
BashDetach {
tool_call_id: String,
reason: String,
},
RunningBash,
}
impl ControlOp {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Model => "model",
Self::RoutedModel => "routed-model",
Self::SetModel { .. } => "set-model",
Self::SetDefaultModel { .. } => "set-default-model",
Self::ThinkingLevel => "thinking-level",
Self::SetThinkingLevel(_) => "set-thinking-level",
Self::ModelsList => "models-list",
Self::ScopedModels => "scoped-models",
Self::RunState => "run-state",
Self::IsIdle => "is-idle",
Self::HasPendingMessages => "has-pending-messages",
Self::ContextUsage => "context-usage",
Self::SessionStats => "session-stats",
Self::SystemPrompt => "system-prompt",
Self::IsProjectTrusted => "is-project-trusted",
Self::GetSettings => "get-settings",
Self::Abort => "abort",
Self::Compact { .. } => "compact",
Self::Shutdown => "shutdown",
Self::SystemPromptOptions => "system-prompt-options",
Self::WaitForIdle => "wait-for-idle",
Self::NewSession { .. } => "new-session",
Self::Fork { .. } => "fork",
Self::SwitchSession { .. } => "switch-session",
Self::NavigateTree { .. } => "navigate-tree",
Self::Reload => "reload",
Self::ActiveTools => "active-tools",
Self::SetActiveTools(_) => "set-active-tools",
Self::AllTools => "all-tools",
Self::CommandsList => "commands-list",
Self::CommandInvoke { .. } => "command-invoke",
Self::ExecuteTool { .. } => "execute-tool",
Self::BashDetach { .. } => "bash-detach",
Self::RunningBash => "running-bash",
}
}
#[must_use]
pub fn needs_session_control(&self) -> bool {
matches!(
self,
Self::SetModel { .. }
| Self::SetDefaultModel { .. }
| Self::SetThinkingLevel(_)
| Self::Abort
| Self::Compact { .. }
| Self::Shutdown
| Self::SetActiveTools(_)
| Self::NewSession { .. }
| Self::Fork { .. }
| Self::SwitchSession { .. }
| Self::NavigateTree { .. }
| Self::Reload
)
}
#[must_use]
pub fn needs_command_context(&self) -> bool {
matches!(
self,
Self::SystemPromptOptions
| Self::WaitForIdle
| Self::NewSession { .. }
| Self::Fork { .. }
| Self::SwitchSession { .. }
| Self::NavigateTree { .. }
| Self::Reload
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ControlReply {
Unit,
Bool(bool),
String(String),
Strings(Vec<String>),
Json(RawJson),
MaybeJson(Option<RawJson>),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Credential {
pub headers: Vec<(String, String)>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_url: Option<String>,
pub provider: String,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ModelsOp {
Complete {
request: RawJson,
cancel: Option<CancellationToken>,
},
Stream {
request: RawJson,
},
SummarizeDefault {
messages: RawJson,
custom_instructions: Option<String>,
previous_summary: Option<String>,
},
CredentialFor(String),
Classify(RawJson),
GenerateImage(RawJson),
}
pub enum ModelsReply {
Json(RawJson),
Stream(BoxStream<'static, String>),
Credential(Option<Credential>),
}
impl fmt::Debug for ModelsReply {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Json(json) => f.debug_tuple("Json").field(json).finish(),
Self::Stream(_) => f.write_str("Stream(..)"),
Self::Credential(c) => f.debug_tuple("Credential").field(c).finish(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum PanelOp {
Open(RawJson),
Update {
panel_id: String,
spec: RawJson,
},
Close(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EditorOp {
Text,
Set(String),
Insert(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeviceOp {
OsNotify {
title: String,
body: String,
urgency: String,
action_url: Option<String>,
},
ClipboardWriteText(String),
ClipboardReadImage,
OpenUrl(String),
EditExternally {
text: String,
extension_hint: Option<String>,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum TaskOp {
Start(RawJson),
AdoptBash {
tool_call_id: String,
reason: String,
},
List,
Get(String),
Logs {
id: String,
max_bytes: u32,
tail: bool,
},
Kill(String),
ClearFinished,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SecretOp {
Get(String),
Set {
name: String,
value: String,
},
Delete(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SettingsScope {
Global,
Project,
}
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub enum SettingsLayer {
#[default]
Effective,
Global,
Project,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ProviderHook {
BeforeRequest {
request_id: String,
payload: RawJson,
headers: Vec<(String, String)>,
},
AfterResponse {
request_id: String,
status: u16,
headers: Vec<(String, String)>,
},
StreamEvent {
request_id: String,
data: RawJson,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum ProviderHookReply {
BeforeRequest {
payload: RawJson,
headers: Vec<(String, String)>,
},
Done,
}
impl ProviderHook {
#[must_use]
pub fn passthrough(&self) -> ProviderHookReply {
match self {
Self::BeforeRequest {
payload, headers, ..
} => ProviderHookReply::BeforeRequest {
payload: payload.clone(),
headers: headers.clone(),
},
Self::AfterResponse { .. } | Self::StreamEvent { .. } => ProviderHookReply::Done,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceInfo {
pub id: String,
pub root: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_root: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_branch: Option<String>,
pub cwd: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HostContextInfo {
pub frontends: Vec<String>,
pub frontend_focused: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workspace: Option<WorkspaceInfo>,
}