Skip to main content

phi_agent/bridge/
messages.rs

1//! NDJSON protocol message types shared between phi-agent and language SDKs.
2//!
3//! This module has **zero** dependency on `agent-base` — it is a pure serde
4//! contract.  SDK authors can use this file as the authoritative reference for
5//! the wire format without pulling in the entire Rust crate.
6//!
7//! # Protocol overview
8//!
9//! - **Transport**: stdio, one JSON object per line (NDJSON).
10//! - **Schema rule**: new fields may be added at any time (receivers MUST ignore
11//!   unknown fields).  Removing or re-typing a field is a MAJOR version change.
12//!
13//! # Message flow
14//!
15//! ```text
16//! SDK → phi serve         SDK ← phi serve
17//! ─────────────────       ─────────────────
18//! register_tool           hello (on connect)
19//! create_session          session_created
20//! run                     event
21//! tool_result             tool_call
22//! list_tools              tools_listed
23//! cancel                  done
24//!                         error
25//! ```
26
27use agent_base::ToolMetadata as AgentToolMetadata;
28use serde::{Deserialize, Serialize};
29use serde_json::Value;
30
31pub const PROTOCOL_VERSION: u32 = 1;
32
33// ── Tool metadata (bridge-facing, mirrors agent_base::ToolMetadata) ────
34
35/// Stable wire-format representation of a registered tool's metadata.
36/// Mirrors `agent_base::ToolMetadata` without depending on agent-base so
37/// SDK authors can read this file as a pure serde contract.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ToolMetadata {
40    pub name: String,
41    pub description: String,
42    pub origin: String,
43    pub version: String,
44    pub requirements: Vec<String>,
45}
46
47impl From<AgentToolMetadata> for ToolMetadata {
48    fn from(m: AgentToolMetadata) -> Self {
49        Self {
50            name: m.name,
51            description: m.description,
52            origin: m.origin,
53            version: m.version,
54            requirements: m.requirements,
55        }
56    }
57}
58
59// ── Incoming (SDK → phi serve) ────────────────────────────────────────
60
61#[derive(Debug, Deserialize)]
62#[serde(tag = "type", rename_all = "snake_case")]
63pub enum IncomingMessage {
64    RegisterTool {
65        name: String,
66        description: String,
67        parameters: Value,
68    },
69    CreateSession {
70        #[serde(default)]
71        session_id: Option<String>,
72    },
73    Run {
74        #[serde(default)]
75        session_id: String,
76        query: String,
77        #[serde(default)]
78        config: Option<RunConfig>,
79    },
80    ToolResult {
81        call_id: String,
82        summary: String,
83        #[serde(default)]
84        raw: Option<Value>,
85        #[serde(default)]
86        control_flow: Option<String>,
87    },
88    Cancel {
89        #[serde(default)]
90        session_id: String,
91    },
92    ListTools {},
93}
94
95#[derive(Debug, Deserialize, Default)]
96pub struct RunConfig {
97    #[serde(default)]
98    pub model: Option<String>,
99    #[serde(default)]
100    pub api_key: Option<String>,
101    #[serde(default)]
102    pub base_url: Option<String>,
103    pub enable_thinking: Option<bool>,
104    pub thinking_budget: Option<u64>,
105    pub thinking_effort: Option<String>,
106    pub max_tool_calls_per_turn: Option<usize>,
107    pub max_consecutive_failures: Option<usize>,
108    pub max_turns: Option<u32>,
109}
110
111// ── Outgoing (phi serve → SDK) ────────────────────────────────────────
112
113#[derive(Debug, Serialize)]
114#[serde(tag = "type", rename_all = "snake_case")]
115pub enum OutgoingMessage {
116    Hello {
117        protocol_version: u32,
118        server_name: String,
119        server_version: String,
120    },
121    SessionCreated {
122        session_id: Option<String>,
123        internal_id: u64,
124    },
125    Event {
126        seq: u64,
127        #[serde(flatten)]
128        event: Value,
129    },
130    ToolCall {
131        seq: u64,
132        call_id: String,
133        name: String,
134        args: Value,
135    },
136    ToolRegistered {
137        name: String,
138        ok: bool,
139    },
140    Done {
141        seq: u64,
142        outcome: String,
143        #[serde(skip_serializing_if = "Option::is_none")]
144        error: Option<String>,
145        #[serde(skip_serializing_if = "Option::is_none")]
146        turns: Option<u32>,
147    },
148    Error {
149        code: String,
150        message: String,
151        #[serde(skip_serializing_if = "Option::is_none")]
152        detail: Option<Value>,
153    },
154    ToolsListed {
155        tools: Vec<ToolMetadata>,
156    },
157}