1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Dependency-inversion context for the agent-session-orchestration cluster.
//!
//! The session/event/title/gold modules that live in this crate need a handful
//! of shared application resources (sessions cache, storage, persistence,
//! providers, the per-session event senders, and the account change-feed sink).
//! Historically those were reached through the server's `AppState`, which
//! coupled the cluster to the actix-web server crate.
//!
//! [`AgentSessionContext`] inverts that dependency: the cluster depends only on
//! this trait, and the server implements it on `AppState`. This keeps the
//! orchestration code free of any HTTP / server types.
//!
//! Accessor return types are deliberately the *exact* field types `AppState`
//! already exposes, so the server-side impl is a thin delegation and no copying
//! is introduced. Provider lookups return `Option` (the server's fallible
//! lookups discard the error type, which is actix-coupled).
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::{broadcast, RwLock};
use bamboo_agent_core::storage::Storage;
use bamboo_agent_core::{AgentEvent, Session};
use bamboo_domain::ProviderModelRef;
use bamboo_llm::{Config, LLMError, LLMProvider, ProviderRegistry};
use bamboo_storage::LockedSessionStore;
use crate::events::AccountEventSink;
use crate::runtime::execution::runner_state::AgentRunner;
#[cfg(test)]
#[path = "app_context/model_routing_test_support.rs"]
pub(crate) mod model_routing_test_support;
/// Shared application context the agent-session-orchestration cluster depends
/// on, in place of the server's concrete `AppState`.
///
/// Implemented by the server on `AppState`. Every accessor mirrors an existing
/// `AppState` field or method; the trait exists solely to invert the
/// cluster→server dependency.
#[async_trait]
pub trait AgentSessionContext: Send + Sync {
/// In-memory session cache.
fn sessions(&self) -> &crate::SessionCache;
/// Persistent session storage backend.
fn storage(&self) -> &Arc<dyn Storage>;
/// Per-session write-serialising persistence layer.
fn persistence(&self) -> &Arc<LockedSessionStore>;
/// Active agent runners, keyed by session id.
fn agent_runners(&self) -> &Arc<RwLock<HashMap<String, AgentRunner>>>;
/// Account-wide durable change-feed sink.
fn account_sink(&self) -> &Arc<AccountEventSink>;
/// Hot-reloadable application configuration.
fn config(&self) -> &Arc<RwLock<Config>>;
/// Multi-provider registry.
fn provider_registry(&self) -> &Arc<ProviderRegistry>;
/// Get (or create) the long-lived broadcast sender for a session's events.
async fn get_session_event_sender(&self, session_id: &str) -> broadcast::Sender<AgentEvent>;
/// Load a session, merging the in-memory and on-disk copies.
async fn load_session_merged(&self, session_id: &str) -> Option<Session>;
/// Persist a session and refresh the in-memory cache.
async fn save_and_cache_session(&self, session: &mut Session);
/// Get the always-current default provider handle.
async fn get_provider(&self) -> Arc<dyn LLMProvider>;
/// Route to a provider for a specific provider/model ref.
///
/// Returns `None` on failure (the server's fallible lookup discards its
/// actix-coupled error type).
fn get_provider_for_model_ref(&self, target: &ProviderModelRef)
-> Option<Arc<dyn LLMProvider>>;
/// Route against a caller-held config snapshot. Standalone embedders retain
/// their existing override; hosts may enforce config-managed provenance.
/// This synchronous method must not attempt to reacquire the config lock.
fn get_provider_for_model_ref_with_config(
&self,
_config: &Config,
target: &ProviderModelRef,
) -> Result<Option<Arc<dyn LLMProvider>>, LLMError> {
Ok(self.get_provider_for_model_ref(target))
}
/// Hosts with config-bound synchronous routing opt in. The default keeps
/// legacy embedder callbacks outside the config lock.
fn resolve_provider_ref_under_config_lock(&self) -> bool {
false
}
/// Resolve a provider for a named provider endpoint.
///
/// Returns `None` on failure (see [`Self::get_provider_for_model_ref`]).
async fn get_provider_for_endpoint(&self, provider_name: &str) -> Option<Arc<dyn LLMProvider>>;
/// Try to claim the title-generation slot for `session_id`.
/// Returns `true` on success, `false` if generation is already in flight.
fn title_gen_acquire(&self, session_id: &str) -> bool;
/// Release the title-generation slot for `session_id`. Idempotent.
fn title_gen_release(&self, session_id: &str);
}