1use std::collections::{BTreeMap, VecDeque};
9#[cfg(feature = "adapter-api")]
10use std::sync::atomic::AtomicBool;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::Arc;
13#[cfg(feature = "adapter-api")]
14use std::sync::Weak;
15
16use async_trait::async_trait;
17#[cfg(feature = "adapter-api")]
18use futures::StreamExt;
19use serde::{Deserialize, Serialize};
20#[cfg(feature = "adapter-api")]
21use serde_json::json;
22use serde_json::Value;
23use tokio::sync::broadcast;
24
25#[cfg(feature = "adapter-api")]
26use crate::sdk::RuntimeSubmitError;
27pub use crate::sdk::SdkError as FrontendRuntimeError;
28pub use crate::sdk::SdkEvent as FrontendEvent;
29pub use crate::sdk::SdkRuntime as FrontendRuntime;
30use crate::server::RpcEngine;
31use crate::ChatMessage;
32
33pub const FRONTEND_RUNTIME_SCHEMA_VERSION: u32 = 2;
35
36pub(crate) const FRONTEND_EVENT_SCHEMA_VERSION: u32 = 1;
41
42pub const FRONTEND_REPLAY_CAPACITY: usize = 4096;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "snake_case")]
48pub enum FrontendTurnState {
49 Idle,
51 Busy,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum FrontendConnectionState {
59 Connected,
61 ShuttingDown,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct FrontendActions {
68 pub submit: bool,
70 pub interrupt: bool,
72 pub steer: bool,
74 pub respond: bool,
76 pub detach: bool,
78 pub close: bool,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct FrontendDisplayCapabilities {
85 pub event_kinds: Vec<String>,
87 pub opaque_fallback: bool,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct FrontendCommandDescriptor {
94 pub name: String,
96 pub description: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub argument_hint: Option<String>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "snake_case")]
111pub enum FrontendOperationKind {
112 Prompt,
114 File,
116 Model,
118 Session,
120 Subagent,
122 Image,
124 Reduction,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
130pub struct FrontendOperationDescriptor {
131 pub id: String,
133 pub kind: FrontendOperationKind,
135 pub command: Option<FrontendCommandDescriptor>,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(tag = "kind", rename_all = "snake_case")]
142pub enum FrontendOperationInvocation {
143 Prompt {
145 operation_id: String,
147 arguments: String,
149 },
150}
151
152impl FrontendOperationInvocation {
153 pub fn operation_id(&self) -> &str {
155 match self {
156 Self::Prompt { operation_id, .. } => operation_id,
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163#[serde(tag = "kind", rename_all = "snake_case")]
164pub enum FrontendOperationResult {
165 Prompt {
167 reply: String,
169 },
170}
171
172#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
174pub struct FrontendRuntimeMetadata {
175 pub source_harness: Option<String>,
177 pub emulation_profile: Option<String>,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183pub struct FrontendRuntimeDescriptor {
184 pub schema_version: u32,
186 pub session_id: String,
188 pub source_harness: Option<String>,
190 pub emulation_profile: Option<String>,
192 pub active_modules: Vec<String>,
194 pub commands: Vec<FrontendCommandDescriptor>,
196 #[serde(default)]
198 pub operations: Vec<FrontendOperationDescriptor>,
199 pub actions: FrontendActions,
201 pub display: FrontendDisplayCapabilities,
203 pub model: String,
205 pub turn_state: FrontendTurnState,
207 pub connection_state: FrontendConnectionState,
209 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
211 pub extensions: BTreeMap<String, Value>,
212}
213
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct FrontendAttachSnapshot {
218 pub descriptor: FrontendRuntimeDescriptor,
220 pub history: Vec<ChatMessage>,
222 pub history_cursor: u64,
224 pub replay: VecDeque<FrontendEvent>,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(rename_all = "snake_case")]
231pub enum FrontendRequestKind {
232 Approval,
234 Elicitation,
236 #[serde(other)]
239 Other,
240}
241
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub struct FrontendRequest {
245 pub id: u64,
247 pub kind: FrontendRequestKind,
249 pub payload: Value,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
255#[serde(rename_all = "snake_case")]
256pub enum FrontendApprovalDecision {
257 Deny,
259 Allow,
261 AllowForSession,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
267#[serde(rename_all = "snake_case")]
268pub enum FrontendElicitationAction {
269 Accept,
271 Decline,
273 Cancel,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279#[serde(tag = "kind", rename_all = "snake_case")]
280pub enum FrontendResponse {
281 Approval {
283 request_id: u64,
285 decision: FrontendApprovalDecision,
287 },
288 Elicitation {
290 request_id: u64,
292 action: FrontendElicitationAction,
294 content: Option<Value>,
296 },
297 Other {
299 request_id: u64,
301 action: FrontendElicitationAction,
303 content: Option<Value>,
305 },
306}
307
308impl FrontendResponse {
309 pub(crate) fn request_id(&self) -> u64 {
310 match self {
311 Self::Approval { request_id, .. }
312 | Self::Elicitation { request_id, .. }
313 | Self::Other { request_id, .. } => *request_id,
314 }
315 }
316}
317
318pub struct FrontendAttachment {
320 pub descriptor: FrontendRuntimeDescriptor,
322 pub history: Vec<ChatMessage>,
324 pub history_cursor: u64,
326 pub(crate) replay: VecDeque<FrontendEvent>,
327 live: broadcast::Receiver<FrontendEvent>,
328 delivered: u64,
329 acknowledged: Option<Arc<AtomicU64>>,
330 _transport_lease: Option<Arc<()>>,
331}
332
333impl FrontendAttachment {
334 pub fn from_snapshot(
338 snapshot: FrontendAttachSnapshot,
339 live: broadcast::Receiver<FrontendEvent>,
340 ) -> Self {
341 Self::from_snapshot_after(snapshot, live, 0)
342 }
343
344 pub fn from_snapshot_after(
349 snapshot: FrontendAttachSnapshot,
350 live: broadcast::Receiver<FrontendEvent>,
351 acknowledged_sequence: u64,
352 ) -> Self {
353 let delivered = snapshot.history_cursor.max(acknowledged_sequence);
354 Self::new_with_delivered(
355 snapshot.descriptor,
356 snapshot.history,
357 snapshot.history_cursor,
358 snapshot.replay,
359 live,
360 None,
361 delivered,
362 )
363 }
364
365 pub(crate) fn new(
366 descriptor: FrontendRuntimeDescriptor,
367 history: Vec<ChatMessage>,
368 history_cursor: u64,
369 replay: VecDeque<FrontendEvent>,
370 live: broadcast::Receiver<FrontendEvent>,
371 transport_lease: Option<Arc<()>>,
372 ) -> Self {
373 let delivered = history_cursor;
374 Self::new_with_delivered(
375 descriptor,
376 history,
377 history_cursor,
378 replay,
379 live,
380 transport_lease,
381 delivered,
382 )
383 }
384
385 fn new_with_delivered(
386 descriptor: FrontendRuntimeDescriptor,
387 history: Vec<ChatMessage>,
388 history_cursor: u64,
389 replay: VecDeque<FrontendEvent>,
390 live: broadcast::Receiver<FrontendEvent>,
391 transport_lease: Option<Arc<()>>,
392 delivered: u64,
393 ) -> Self {
394 Self {
395 descriptor,
396 history,
397 history_cursor,
398 replay,
399 live,
400 delivered,
401 acknowledged: None,
402 _transport_lease: transport_lease,
403 }
404 }
405
406 #[cfg(feature = "adapter-acp")]
407 pub(crate) fn with_acknowledgement(mut self, acknowledged: Arc<AtomicU64>) -> Self {
408 acknowledged.fetch_max(self.history_cursor, Ordering::SeqCst);
409 self.acknowledged = Some(acknowledged);
410 self
411 }
412
413 fn acknowledge(&self, event: &FrontendEvent) {
414 if !event_advances_acknowledgement(event) {
415 return;
416 }
417 if let Some(acknowledged) = &self.acknowledged {
418 acknowledged.fetch_max(event.sequence, Ordering::SeqCst);
419 }
420 }
421
422 pub async fn next_event(&mut self) -> Result<FrontendEvent, FrontendRuntimeError> {
426 loop {
427 let event = match self.next_replay_event() {
428 Some(event) => return Ok(event),
429 None => match self.live.recv().await {
430 Ok(event) => event,
431 Err(broadcast::error::RecvError::Lagged(count)) => {
432 return Err(FrontendRuntimeError::ReplayGap(count));
433 }
434 Err(broadcast::error::RecvError::Closed) => {
435 return Err(FrontendRuntimeError::Closed);
436 }
437 },
438 };
439 if event.sequence <= self.delivered {
440 continue;
441 }
442 self.delivered = event.sequence;
443 self.acknowledge(&event);
444 return Ok(event);
445 }
446 }
447
448 pub fn next_replay_event(&mut self) -> Option<FrontendEvent> {
453 while let Some(event) = self.replay.pop_front() {
454 if event.sequence <= self.delivered {
455 continue;
456 }
457 self.delivered = event.sequence;
458 self.acknowledge(&event);
459 return Some(event);
460 }
461 None
462 }
463}
464
465pub(crate) fn event_advances_acknowledgement(event: &FrontendEvent) -> bool {
466 event
467 .payload
468 .pointer("/_meta/supercode/transient")
469 .and_then(Value::as_bool)
470 != Some(true)
471}
472
473pub(crate) struct FrontendProjectionState {
475 pub(crate) history: Vec<ChatMessage>,
476 pub(crate) history_cursor: u64,
477 pub(crate) next_sequence: u64,
478 pub(crate) replay: VecDeque<FrontendEvent>,
479}
480
481#[async_trait]
482impl FrontendRuntime for RpcEngine {
483 async fn describe(&self) -> Result<FrontendRuntimeDescriptor, FrontendRuntimeError> {
484 Ok(self.frontend_descriptor())
485 }
486
487 async fn attach(
488 &self,
489 history_limit: usize,
490 ) -> Result<FrontendAttachment, FrontendRuntimeError> {
491 self.frontend_attach(history_limit)
492 }
493
494 async fn send_input(self: Arc<Self>, prompt: String) -> Result<(), FrontendRuntimeError> {
495 RpcEngine::send_input(&self, prompt)?;
496 Ok(())
497 }
498
499 async fn submit(&self, prompt: String) -> Result<String, FrontendRuntimeError> {
500 Ok(RpcEngine::submit(self, prompt).await?)
501 }
502
503 async fn submit_with_images(
504 &self,
505 prompt: String,
506 image_urls: Vec<String>,
507 ) -> Result<String, FrontendRuntimeError> {
508 Ok(RpcEngine::submit_with_images(self, prompt, image_urls).await?)
509 }
510
511 async fn interrupt(&self) -> Result<bool, FrontendRuntimeError> {
512 Ok(RpcEngine::interrupt(self).await)
513 }
514
515 async fn steer(&self, prompt: String) -> Result<(), FrontendRuntimeError> {
516 RpcEngine::steer(self, prompt)
517 }
518
519 async fn respond(&self, response: FrontendResponse) -> Result<(), FrontendRuntimeError> {
520 RpcEngine::respond(self, response)
521 }
522
523 async fn invoke(
524 &self,
525 operation: FrontendOperationInvocation,
526 ) -> Result<FrontendOperationResult, FrontendRuntimeError> {
527 RpcEngine::invoke(self, operation).await
528 }
529
530 async fn close(&self) -> Result<(), FrontendRuntimeError> {
531 RpcEngine::shutdown(self).await;
532 Ok(())
533 }
534}
535
536#[cfg(feature = "adapter-api")]
541pub struct HttpFrontendRuntime {
542 base_url: String,
543 token: String,
544 client_id: crate::RuntimeClientId,
545 authorization: crate::RuntimeAuthorization,
546 client: reqwest::Client,
547 events: broadcast::Sender<FrontendEvent>,
548 next_id: AtomicU64,
549 lifecycle: Arc<()>,
550 disconnected: AtomicBool,
551}
552
553#[cfg(feature = "adapter-api")]
554impl HttpFrontendRuntime {
555 pub async fn connect(
558 base_url: impl Into<String>,
559 token: impl Into<String>,
560 ) -> Result<Arc<Self>, FrontendRuntimeError> {
561 let mut random = [0_u8; 16];
562 getrandom::getrandom(&mut random).map_err(|error| {
563 FrontendRuntimeError::Transport(format!(
564 "cannot generate runtime client identity: {error}"
565 ))
566 })?;
567 let suffix = random
568 .iter()
569 .map(|byte| format!("{byte:02x}"))
570 .collect::<String>();
571 let client_id = crate::RuntimeClientId::parse(format!("http-{suffix}"))
572 .map_err(|error| FrontendRuntimeError::Transport(error.to_string()))?;
573 Self::connect_with_client_id(base_url, token, client_id).await
574 }
575
576 pub async fn connect_with_client_id(
579 base_url: impl Into<String>,
580 token: impl Into<String>,
581 client_id: crate::RuntimeClientId,
582 ) -> Result<Arc<Self>, FrontendRuntimeError> {
583 Self::connect_with_authorization(
584 base_url,
585 token,
586 client_id,
587 crate::RuntimeAuthorization::owner(),
588 )
589 .await
590 }
591
592 pub async fn connect_with_authorization(
596 base_url: impl Into<String>,
597 token: impl Into<String>,
598 client_id: crate::RuntimeClientId,
599 authorization: crate::RuntimeAuthorization,
600 ) -> Result<Arc<Self>, FrontendRuntimeError> {
601 Self::connect_inner(base_url, token, client_id, authorization, true)
602 .await
603 .map(|(runtime, _)| runtime)
604 }
605
606 pub(crate) async fn probe_described(
613 base_url: impl Into<String>,
614 token: impl Into<String>,
615 client_id: crate::RuntimeClientId,
616 ) -> Result<(Arc<Self>, FrontendRuntimeDescriptor), FrontendRuntimeError> {
617 Self::connect_inner(
618 base_url,
619 token,
620 client_id,
621 crate::RuntimeAuthorization::observer(),
622 false,
623 )
624 .await
625 }
626
627 async fn connect_inner(
628 base_url: impl Into<String>,
629 token: impl Into<String>,
630 client_id: crate::RuntimeClientId,
631 authorization: crate::RuntimeAuthorization,
632 stream_events: bool,
633 ) -> Result<(Arc<Self>, FrontendRuntimeDescriptor), FrontendRuntimeError> {
634 let runtime = Arc::new(Self {
635 base_url: base_url.into().trim_end_matches('/').to_string(),
636 token: token.into(),
637 client_id,
638 authorization,
639 client: reqwest::Client::new(),
640 events: broadcast::channel(1024).0,
641 next_id: AtomicU64::new(1),
642 lifecycle: Arc::new(()),
643 disconnected: AtomicBool::new(false),
644 });
645 let descriptor: FrontendRuntimeDescriptor = runtime
647 .rpc_typed(crate::FrontendFacadeMethod::Describe.wire_name(), json!({}))
648 .await?;
649 if stream_events {
650 Self::start_event_stream(&runtime).await?;
651 }
652 Ok((runtime, descriptor))
653 }
654
655 async fn start_event_stream(runtime: &Arc<Self>) -> Result<(), FrontendRuntimeError> {
656 let (ready_tx, ready_rx) = tokio::sync::oneshot::channel();
657 let weak = Arc::downgrade(runtime);
658 let lifecycle = Arc::downgrade(&runtime.lifecycle);
659 tokio::spawn(async move {
660 Self::run_event_stream(weak, lifecycle, ready_tx).await;
661 });
662 ready_rx.await.map_err(|_| {
663 FrontendRuntimeError::Transport("frontend event stream exited before startup".into())
664 })?
665 }
666
667 async fn run_event_stream(
668 weak: Weak<Self>,
669 lifecycle: Weak<()>,
670 ready: tokio::sync::oneshot::Sender<Result<(), FrontendRuntimeError>>,
671 ) {
672 let Some(runtime) = weak.upgrade() else {
673 let _ = ready.send(Err(FrontendRuntimeError::Closed));
674 return;
675 };
676 let request = runtime
677 .client
678 .get(format!("{}/frontend/events", runtime.base_url))
679 .bearer_auth(&runtime.token)
680 .header("x-supercode-client-id", runtime.client_id.as_str())
681 .header(
682 "x-supercode-permissions",
683 runtime.authorization.header_value(),
684 );
685 let events = runtime.events.clone();
686 drop(runtime);
687 let response = request.send().await;
688 let response = match response {
689 Ok(response) if response.status().is_success() => response,
690 Ok(response) => {
691 let _ = ready.send(Err(FrontendRuntimeError::Transport(format!(
692 "frontend event stream returned {}",
693 response.status()
694 ))));
695 return;
696 }
697 Err(error) => {
698 let _ = ready.send(Err(FrontendRuntimeError::Transport(error.to_string())));
699 return;
700 }
701 };
702 let _ = ready.send(Ok(()));
703 let mut stream = response.bytes_stream();
704 let mut pending = Vec::<u8>::new();
705 let mut liveness = tokio::time::interval(std::time::Duration::from_millis(100));
706 loop {
707 let chunk = tokio::select! {
708 _ = liveness.tick() => {
709 if lifecycle.strong_count() == 0 {
710 break;
711 }
712 if weak
713 .upgrade()
714 .is_some_and(|runtime| runtime.disconnected.load(Ordering::SeqCst))
715 {
716 break;
717 }
718 continue;
719 }
720 chunk = stream.next() => chunk,
721 };
722 let Some(chunk) = chunk else {
723 break;
724 };
725 let Ok(chunk) = chunk else {
726 break;
727 };
728 pending.extend_from_slice(&chunk);
729 while let Some(position) = pending.iter().position(|byte| *byte == b'\n') {
730 let line = pending.drain(..=position).collect::<Vec<_>>();
731 let line = String::from_utf8_lossy(&line);
732 let Some(data) = line.trim_end().strip_prefix("data: ") else {
733 continue;
734 };
735 if let Ok(event) = serde_json::from_str::<FrontendEvent>(data) {
736 let _ = events.send(event);
737 }
738 }
739 }
740 if let Some(runtime) = weak.upgrade() {
741 runtime.disconnected.store(true, Ordering::SeqCst);
742 let _ = runtime.events.send(FrontendEvent::new(
743 u64::MAX,
744 json!({
745 "type": "runtime_disconnected",
746 "schema_version": FRONTEND_EVENT_SCHEMA_VERSION
747 }),
748 ));
749 }
750 }
751
752 async fn rpc(&self, method: &str, params: Value) -> Result<Value, FrontendRuntimeError> {
753 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
754 let requested_operation = params
755 .pointer("/operation/operation_id")
756 .and_then(Value::as_str)
757 .map(str::to_owned);
758 let response = self
759 .client
760 .post(format!("{}/rpc", self.base_url))
761 .bearer_auth(&self.token)
762 .header("x-supercode-client-id", self.client_id.as_str())
763 .header("x-supercode-permissions", self.authorization.header_value())
764 .json(&json!({"id": id, "method": method, "params": params}))
765 .send()
766 .await
767 .map_err(|error| FrontendRuntimeError::Transport(error.to_string()))?;
768 if !response.status().is_success() {
769 return Err(FrontendRuntimeError::Transport(format!(
770 "SDK HTTP RPC returned {}",
771 response.status()
772 )));
773 }
774 let value: Value = response
775 .json()
776 .await
777 .map_err(|error| FrontendRuntimeError::Transport(error.to_string()))?;
778 if let Some(error) = value.get("error") {
779 let code = error.get("code").and_then(Value::as_i64);
780 let name = error.get("name").and_then(Value::as_str);
781 let operation = error
782 .get("operation")
783 .and_then(Value::as_str)
784 .and_then(crate::SdkOperation::from_action_name);
785 let message = error
786 .get("message")
787 .and_then(Value::as_str)
788 .unwrap_or("SDK runtime request failed")
789 .to_string();
790 return Err(match (name, code) {
791 (Some("unauthenticated"), _) | (_, Some(-32030)) => {
792 FrontendRuntimeError::Unauthenticated
793 }
794 (Some("unauthorized"), _) | (_, Some(-32031)) => {
795 FrontendRuntimeError::Unauthorized {
796 permission: error
797 .get("permission")
798 .and_then(Value::as_str)
799 .unwrap_or("unknown")
800 .to_string(),
801 }
802 }
803 (Some("controller_required"), _) | (_, Some(-32032)) => {
804 FrontendRuntimeError::ControllerRequired {
805 holder: error
806 .get("holder")
807 .and_then(Value::as_str)
808 .map(str::to_owned),
809 expires_at_ms: error.get("expiresAtMs").and_then(Value::as_u64),
810 }
811 }
812 (Some("lease_expired"), _) | (_, Some(-32033)) => {
813 FrontendRuntimeError::LeaseExpired
814 }
815 (_, Some(-32023)) => FrontendRuntimeError::UnsupportedOperation(
816 requested_operation.unwrap_or(message),
817 ),
818 (Some("unsupported_action"), _) => FrontendRuntimeError::UnsupportedAction(
819 operation
820 .unwrap_or_else(|| {
821 crate::SdkOperation::from_action_name(method)
822 .unwrap_or(crate::SdkOperation::Respond)
823 })
824 .action_name(),
825 ),
826 (Some("not_found"), Some(-32021)) => {
827 let request_id = params
828 .pointer("/response/request_id")
829 .and_then(Value::as_u64)
830 .unwrap_or_default();
831 FrontendRuntimeError::UnknownRequest(request_id)
832 }
833 (Some("invalid_argument"), _) => FrontendRuntimeError::InvalidResponse(message),
834 (_, Some(-32000)) => RuntimeSubmitError::Busy.into(),
835 (_, Some(-32001)) => RuntimeSubmitError::Interrupted.into(),
836 (_, Some(-32002)) => RuntimeSubmitError::Agent(message).into(),
837 (_, Some(-32020)) => FrontendRuntimeError::UnsupportedAction(
838 crate::SdkOperation::from_action_name(method)
839 .unwrap_or(crate::SdkOperation::Respond)
840 .action_name(),
841 ),
842 (_, Some(-32021)) => {
843 let request_id = params
844 .pointer("/response/request_id")
845 .and_then(Value::as_u64)
846 .unwrap_or_default();
847 FrontendRuntimeError::UnknownRequest(request_id)
848 }
849 (_, Some(-32022)) => FrontendRuntimeError::InvalidResponse(message),
850 _ => FrontendRuntimeError::Transport(message),
851 });
852 }
853 Ok(value.get("result").cloned().unwrap_or(Value::Null))
854 }
855
856 async fn rpc_typed<T: serde::de::DeserializeOwned>(
857 &self,
858 method: &str,
859 params: Value,
860 ) -> Result<T, FrontendRuntimeError> {
861 serde_json::from_value(self.rpc(method, params).await?)
862 .map_err(|error| FrontendRuntimeError::Transport(error.to_string()))
863 }
864
865 pub fn client_id(&self) -> &crate::RuntimeClientId {
867 &self.client_id
868 }
869
870 pub fn is_disconnected(&self) -> bool {
873 self.disconnected.load(Ordering::SeqCst)
874 }
875
876 pub async fn take_control(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
879 self.rpc_typed(
880 crate::FrontendFacadeMethod::TakeControl.wire_name(),
881 json!({}),
882 )
883 .await
884 }
885
886 pub async fn heartbeat(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
888 self.rpc_typed(
889 crate::FrontendFacadeMethod::Heartbeat.wire_name(),
890 json!({}),
891 )
892 .await
893 }
894
895 pub async fn lease_snapshot(
897 &self,
898 ) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
899 self.rpc_typed(crate::FrontendFacadeMethod::Lease.wire_name(), json!({}))
900 .await
901 }
902
903 pub async fn detach(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
905 let snapshot = self
906 .rpc_typed(crate::FrontendFacadeMethod::Detach.wire_name(), json!({}))
907 .await?;
908 self.disconnected.store(true, Ordering::SeqCst);
909 Ok(snapshot)
910 }
911}
912
913#[async_trait]
914#[cfg(feature = "adapter-api")]
915impl FrontendRuntime for HttpFrontendRuntime {
916 async fn describe(&self) -> Result<FrontendRuntimeDescriptor, FrontendRuntimeError> {
917 self.rpc_typed(crate::FrontendFacadeMethod::Describe.wire_name(), json!({}))
918 .await
919 }
920
921 async fn attach(
922 &self,
923 history_limit: usize,
924 ) -> Result<FrontendAttachment, FrontendRuntimeError> {
925 if self.disconnected.load(Ordering::SeqCst) {
926 return Err(FrontendRuntimeError::Closed);
927 }
928 let live = self.events.subscribe();
932 let snapshot: FrontendAttachSnapshot = self
933 .rpc_typed(
934 crate::FrontendFacadeMethod::Attach.wire_name(),
935 json!({"limit": history_limit}),
936 )
937 .await?;
938 Ok(FrontendAttachment::new(
939 snapshot.descriptor,
940 snapshot.history,
941 snapshot.history_cursor,
942 snapshot.replay,
943 live,
944 Some(self.lifecycle.clone()),
945 ))
946 }
947
948 async fn send_input(self: Arc<Self>, prompt: String) -> Result<(), FrontendRuntimeError> {
949 self.rpc(
950 crate::FrontendFacadeMethod::SendInput.wire_name(),
951 json!({"prompt": prompt}),
952 )
953 .await?;
954 Ok(())
955 }
956
957 async fn submit(&self, prompt: String) -> Result<String, FrontendRuntimeError> {
958 let result = self
959 .rpc(
960 crate::FrontendFacadeMethod::Submit.wire_name(),
961 json!({"prompt": prompt}),
962 )
963 .await?;
964 Ok(result
965 .get("reply")
966 .and_then(Value::as_str)
967 .unwrap_or_default()
968 .to_string())
969 }
970
971 async fn submit_with_images(
972 &self,
973 prompt: String,
974 image_urls: Vec<String>,
975 ) -> Result<String, FrontendRuntimeError> {
976 let result = self
977 .rpc(
978 crate::FrontendFacadeMethod::Submit.wire_name(),
979 json!({"prompt": prompt, "image_urls": image_urls}),
980 )
981 .await?;
982 Ok(result
983 .get("reply")
984 .and_then(Value::as_str)
985 .unwrap_or_default()
986 .to_string())
987 }
988
989 async fn interrupt(&self) -> Result<bool, FrontendRuntimeError> {
990 let result = self
991 .rpc(
992 crate::FrontendFacadeMethod::Interrupt.wire_name(),
993 json!({}),
994 )
995 .await?;
996 Ok(result
997 .get("interrupted")
998 .and_then(Value::as_bool)
999 .unwrap_or(false))
1000 }
1001
1002 async fn steer(&self, prompt: String) -> Result<(), FrontendRuntimeError> {
1003 self.rpc(
1004 crate::FrontendFacadeMethod::Steer.wire_name(),
1005 json!({"prompt": prompt}),
1006 )
1007 .await?;
1008 Ok(())
1009 }
1010
1011 async fn respond(&self, response: FrontendResponse) -> Result<(), FrontendRuntimeError> {
1012 self.rpc(
1013 crate::FrontendFacadeMethod::Respond.wire_name(),
1014 json!({"response": response}),
1015 )
1016 .await?;
1017 Ok(())
1018 }
1019
1020 async fn invoke(
1021 &self,
1022 operation: FrontendOperationInvocation,
1023 ) -> Result<FrontendOperationResult, FrontendRuntimeError> {
1024 self.rpc_typed(
1025 crate::FrontendFacadeMethod::Invoke.wire_name(),
1026 json!({"operation": operation}),
1027 )
1028 .await
1029 }
1030
1031 async fn lease_snapshot(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
1032 HttpFrontendRuntime::lease_snapshot(self).await
1033 }
1034
1035 async fn take_control(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
1036 HttpFrontendRuntime::take_control(self).await
1037 }
1038
1039 async fn heartbeat(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
1040 HttpFrontendRuntime::heartbeat(self).await
1041 }
1042
1043 async fn detach(&self) -> Result<crate::RuntimeLeaseSnapshot, FrontendRuntimeError> {
1044 HttpFrontendRuntime::detach(self).await
1045 }
1046
1047 async fn close(&self) -> Result<(), FrontendRuntimeError> {
1048 self.rpc(crate::FrontendFacadeMethod::Close.wire_name(), json!({}))
1049 .await?;
1050 self.disconnected.store(true, Ordering::SeqCst);
1051 Ok(())
1052 }
1053}