Skip to main content

phi_agent/
lib.rs

1//! phi-agent: Rust AI Agent runtime framework — orchestration, sessions, streaming all built-in.
2//! You only define tools, prompts, and domain knowledge.
3//!
4//! Built on agent-base and agent-works, providing builder factory, renderer,
5//! config resolution, session management, and other infrastructure.
6//! **Ships with zero application tools.** Kernel tools (file I/O, shell,
7//! multi-agent) are available via `phi-kernel-tools` behind feature flags —
8//! all off by default. Application tools are injected by consumers.
9
10#![warn(missing_docs)]
11
12pub mod agent;
13pub mod bridge;
14pub mod cli;
15pub mod config;
16pub mod event_log;
17/// System prompt generation (EN/CN).
18pub mod prompt;
19pub mod render;
20/// Session management — ID resolution, locking, snapshots, cleanup.
21pub mod session;
22
23// ── Common agent-base types ──
24// Only re-export the types consumers use most often.
25// For the full type set, import directly from agent-base.
26//
27// Note: AgentBuilder re-exports agent_works::AgentBuilder (not agent_base::AgentBuilder)
28// because phi-agent is a full-stack framework that includes multi-agent, skills, MCP, etc.
29// For the bare runtime builder, use agent_base::AgentBuilder directly.
30pub use agent_base::{
31    AgentError, AgentResult, AgentRuntime, ApprovalDecision, ApprovalHandler, ApprovalRequest,
32    ConsecutiveFailureRecovery, Content, FinishReason, Middleware, PlanItem, PlanStepStatus, PostLlmCtx, PreLlmCtx,
33    ReasoningConfig, ReasoningEffort, RiskLevel, RunOutcome, RuntimeEvent, SafetyConfig, SessionId, Tool, ToolContext,
34    ToolMetadata, ToolPolicy, TurnFactMiddleware, TurnToolLimitMiddleware, UpdatePlanTool, UserMessageCtx,
35};
36pub use agent_works::AgentBuilder;
37
38// ── phi-telemetry (metrics types and storage) ──
39#[cfg(feature = "telemetry")]
40pub use phi_telemetry::{
41    SessionMetrics, SessionOutcome, SessionSummary, TurnMetrics, TurnOutcome, list_all_metrics, load_metrics,
42    save_metrics, try_load_metrics,
43};
44
45// ── agent-works ──
46#[cfg(feature = "focus")]
47pub use agent_works::focus::{Context as FocusContext, Focus, FocusError, FocusInput, FocusOutput};
48#[cfg(feature = "multi-agent")]
49pub use agent_works::multi_agent::{
50    ChildPermissionMode, ChildReport, ChildResultEvent, ControlConfig, MultiAgentConfig,
51};
52
53// ── MCP (feature-gated) ──
54#[cfg(feature = "mcp")]
55pub use agent_works::mcp::{McpServeConfig, McpServer, McpServerConfig, McpServerTransport, McpTransport};
56
57// ── phi-agent types ──
58#[cfg(feature = "compression")]
59pub use agent::CompressionMiddleware;
60pub use agent::{
61    PhiAgent, PhiAgentConfig, base_agent_builder, base_agent_builder_with_excludes, base_agent_builder_with_options,
62    clear_compression_cache, run_compact_session,
63};
64pub use agent_works::prompt::{
65    DynamicToolsFragment, EnvironmentFragment, FragmentContext, PromptFragment, compose_fragments,
66};
67pub use cli::{ApprovalMode, AutoApprovalHandler};
68pub use config::{LlmConfig, resolve_llm_config};
69pub use event_log::{event_to_jsonl, event_to_value, save_turn_log};
70pub use prompt::{build_system_prompt, build_system_prompt_cn, build_system_prompt_with_fragments};
71pub use render::{
72    EventRenderer, JsonStreamRenderer, NullRenderer, OutputFormat, create_renderer, create_stdout_renderer,
73};
74pub use session::{
75    SessionContext, SnapshotInfo, cleanup_expired_sessions, create_snapshot, delete_snapshot, list_snapshots,
76    resolve_session, restore_snapshot, validate_session_id, validate_snapshot_name,
77};
78
79/// Format a number with K/M suffixes for display.
80///
81/// - `n < 1_000` → plain number
82/// - `1_000 <= n < 1_000_000` → `{:.1}K`
83/// - `n >= 1_000_000` → `{:.1}M`
84pub fn format_number(n: u64) -> String {
85    if n >= 1_000_000 {
86        format!("{:.1}M", n as f64 / 1_000_000.0)
87    } else if n >= 1_000 {
88        format!("{:.1}K", n as f64 / 1_000.0)
89    } else {
90        n.to_string()
91    }
92}
93
94#[cfg(test)]
95mod proptests {
96    use super::*;
97
98    proptest::proptest! {
99        #[test]
100        fn format_number_never_panics(n: u64) {
101            let _ = format_number(n);
102        }
103
104        #[test]
105        fn format_number_correct_suffix(n: u64) {
106            let s = format_number(n);
107            if n < 1_000 {
108                proptest::prop_assert_eq!(s, n.to_string());
109            } else if n < 1_000_000 {
110                proptest::prop_assert!(s.ends_with('K'), "expected K suffix for {}, got '{}'", n, s);
111            } else {
112                proptest::prop_assert!(s.ends_with('M'), "expected M suffix for {}, got '{}'", n, s);
113            }
114        }
115
116        #[test]
117        fn format_number_boundary_correct(n in 0u64..1_001) {
118            // At the exact boundary (1000), must switch to K
119            let s = format_number(n);
120            if n < 1_000 {
121                proptest::prop_assert!(!s.contains('K'));
122                proptest::prop_assert!(!s.contains('M'));
123            } else {
124                proptest::prop_assert!(s.ends_with('K'));
125            }
126        }
127    }
128}