1use std::fmt;
7use std::sync::Arc;
8use std::time::Duration;
9
10use async_trait::async_trait;
11use talon_container::{ContainerHandle, ContainerRunner, RunResult, RunSpec};
12use talon_core::protocol::{ContainerOutput, LlmEndpoint, OutputStatus, SessionUsage};
13use talon_core::types::{ContainerConfig, VllmConfig};
14use talon_llm::GatewayHandle;
15use talon_scheduler::RunningContainer;
16use tokio::sync::mpsc;
17
18pub type StartHook = Box<dyn FnOnce(Arc<dyn RunningContainer>) + Send>;
20
21#[async_trait]
23pub trait ContainerLauncher: Send + Sync {
24 async fn launch(
32 &self,
33 spec: RunSpec,
34 outputs: mpsc::Sender<ContainerOutput>,
35 on_start: StartHook,
36 ) -> RunResult;
37
38 fn hard_timeout(&self, config: &ContainerConfig) -> Duration;
41}
42
43pub trait LlmAccess: Send + Sync {
45 fn issue(&self, label: &str, ttl: Duration, vllm: &VllmConfig) -> (String, LlmEndpoint);
51
52 fn revoke(&self, token: &str);
54
55 fn extend(&self, _token: &str, _ttl: Duration) -> bool {
58 false
59 }
60}
61
62pub struct QueueContainer(pub ContainerHandle);
64
65impl fmt::Debug for QueueContainer {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.debug_tuple("QueueContainer").field(&self.0).finish()
68 }
69}
70
71#[async_trait]
72impl RunningContainer for QueueContainer {
73 fn name(&self) -> &str {
74 self.0.name()
75 }
76
77 async fn stop(&self) -> talon_core::Result<()> {
78 self.0.stop().await;
79 Ok(())
80 }
81}
82
83pub struct RunnerLauncher {
85 runner: ContainerRunner,
86}
87
88impl fmt::Debug for RunnerLauncher {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.debug_struct("RunnerLauncher")
91 .field("runner", &self.runner)
92 .finish()
93 }
94}
95
96impl RunnerLauncher {
97 pub fn new(runner: ContainerRunner) -> Self {
99 Self { runner }
100 }
101}
102
103#[async_trait]
104impl ContainerLauncher for RunnerLauncher {
105 async fn launch(
106 &self,
107 spec: RunSpec,
108 outputs: mpsc::Sender<ContainerOutput>,
109 on_start: StartHook,
110 ) -> RunResult {
111 let handler = move |output: ContainerOutput| {
112 let outputs = outputs.clone();
113 async move {
114 let _ = outputs.send(output).await;
119 }
120 };
121 self.runner
122 .run(spec, handler, move |handle: ContainerHandle| {
123 on_start(Arc::new(QueueContainer(handle)));
124 })
125 .await
126 }
127
128 fn hard_timeout(&self, config: &ContainerConfig) -> Duration {
129 self.runner.config().hard_timeout(config)
130 }
131}
132
133#[derive(Debug, Clone)]
135pub struct GatewayLlm {
136 gateway: GatewayHandle,
137 container_host: String,
140}
141
142impl GatewayLlm {
143 pub fn new(gateway: GatewayHandle, container_host: impl Into<String>) -> Self {
145 Self {
146 gateway,
147 container_host: container_host.into(),
148 }
149 }
150}
151
152impl LlmAccess for GatewayLlm {
153 fn issue(&self, label: &str, ttl: Duration, vllm: &VllmConfig) -> (String, LlmEndpoint) {
154 let token = self.gateway.issue_token(label, ttl);
155 let endpoint = self
156 .gateway
157 .endpoint(token.clone(), &self.container_host, vllm);
158 (token, endpoint)
159 }
160
161 fn revoke(&self, token: &str) {
162 self.gateway.revoke(token);
163 }
164
165 fn extend(&self, token: &str, ttl: Duration) -> bool {
166 self.gateway.extend(token, ttl)
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct AgentOutcome {
173 pub status: OutputStatus,
175 pub error: Option<String>,
177 pub last_result: Option<String>,
179 pub new_session_id: Option<String>,
181 pub usage: Option<SessionUsage>,
183 pub launched: bool,
185}
186
187impl AgentOutcome {
188 pub fn not_started(error: impl Into<String>) -> Self {
190 Self {
191 status: OutputStatus::Error,
192 error: Some(error.into()),
193 last_result: None,
194 new_session_id: None,
195 usage: None,
196 launched: false,
197 }
198 }
199
200 pub fn is_success(&self) -> bool {
202 self.status == OutputStatus::Success
203 }
204}