-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathlifecycle_observer.rs
More file actions
120 lines (107 loc) · 4.87 KB
/
Copy pathlifecycle_observer.rs
File metadata and controls
120 lines (107 loc) · 4.87 KB
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
114
115
116
117
118
119
120
//! Observe lifecycle and event traffic without owning permission decisions.
//!
//! Demonstrates the channel-based observer APIs:
//!
//! - [`Client::subscribe_lifecycle`] — `tokio::sync::broadcast::Receiver` of
//! every `session.lifecycle` notification (created / destroyed / errored /
//! foreground / background). Filter by matching on `event.event_type` in
//! the consumer.
//! - [`Session::subscribe`] — receiver for the per-session `session.event`
//! stream (assistant messages, tool calls, permission prompts, etc.).
//! Observe-only — the constructor handler still owns permission decisions.
//! - [`Client::state`] — current connection state without polling.
//! - [`Client::get_session_metadata`] — inspect a session without resuming
//! it.
//! - [`Client::force_stop`] — synchronous shutdown for cleanup paths.
//!
//! Drop the receiver to unsubscribe — there is no separate cancel handle.
//! Slow consumers receive `RecvError::Lagged(n)` and resync on the next
//! event; they do not block the producer.
//!
//! ```sh
//! cargo run -p github-copilot-sdk --example lifecycle_observer
//! ```
//!
//! [`Client::subscribe_lifecycle`]: github_copilot_sdk::Client::subscribe_lifecycle
//! [`Session::subscribe`]: github_copilot_sdk::session::Session::subscribe
//! [`Client::state`]: github_copilot_sdk::Client::state
//! [`Client::get_session_metadata`]: github_copilot_sdk::Client::get_session_metadata
//! [`Client::force_stop`]: github_copilot_sdk::Client::force_stop
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use github_copilot_sdk::handler::ApproveAllHandler;
use github_copilot_sdk::types::{MessageOptions, SessionConfig, SessionLifecycleEventType};
use github_copilot_sdk::{Client, ClientOptions};
#[tokio::main]
async fn main() -> Result<(), github_copilot_sdk::Error> {
let client = Client::start(ClientOptions::default()).await?;
println!("[client] started, pid: {:?}", client.pid());
// Wildcard lifecycle subscriber: see every session.lifecycle event,
// counting deletions inline by filtering on event_type.
let mut lifecycle_rx = client.subscribe_lifecycle();
let deleted = Arc::new(AtomicUsize::new(0));
let deleted_clone = Arc::clone(&deleted);
let lifecycle_task = tokio::spawn(async move {
while let Ok(event) = lifecycle_rx.recv().await {
let summary = event
.metadata
.as_ref()
.and_then(|m| m.summary.as_deref())
.unwrap_or("<no summary>");
println!(
"[lifecycle:*] {:?} session={} summary={}",
event.event_type, event.session_id, summary,
);
if event.event_type == SessionLifecycleEventType::Deleted {
deleted_clone.fetch_add(1, Ordering::Relaxed);
}
}
});
let config = SessionConfig::default().with_permission_handler(Arc::new(ApproveAllHandler));
let session = client.create_session(config).await?;
println!("[client] session created: {}", session.id());
// Per-session observer: see every assistant message, tool call, etc.
// Subscribers fire alongside the constructor handler; they're great for
// logging or metrics that should run regardless of how the handler
// decides to respond.
let mut session_rx = session.subscribe();
let session_events = Arc::new(AtomicUsize::new(0));
let session_events_clone = Arc::clone(&session_events);
let session_task = tokio::spawn(async move {
while let Ok(event) = session_rx.recv().await {
session_events_clone.fetch_add(1, Ordering::Relaxed);
println!("[session-event] {}", event.event_type);
}
});
if let Some(metadata) = client.get_session_metadata(session.id()).await? {
println!(
"[metadata] id={} modified={} summary={}",
metadata.session_id,
metadata.modified_time,
metadata.summary.as_deref().unwrap_or("<no summary>"),
);
}
session
.send_and_wait(
MessageOptions::new("Say hello in five words or fewer.")
.with_wait_timeout(Duration::from_secs(60)),
)
.await?;
session.disconnect().await?;
// Synchronous shutdown — useful in panicking-cleanup paths or tests
// where you don't have an async runtime available to await `stop()`.
// For graceful shutdown in normal flow, prefer `client.stop().await`.
client.force_stop();
println!("[client] force-stopped");
// Stopping the client closes the broadcast senders, so the consumer
// tasks observe `RecvError::Closed` and exit cleanly.
let _ = lifecycle_task.await;
let _ = session_task.await;
println!(
"\n[summary] session_events={} sessions_deleted={}",
session_events.load(Ordering::Relaxed),
deleted.load(Ordering::Relaxed),
);
Ok(())
}