Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/commands/agent_logs.rs
13383 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
use ahp::SubscriptionEvent;
7
use ahp_types::actions::StateAction;
8
use ahp_types::commands::{SubscribeParams, SubscribeResult};
9
use ahp_types::state::{SnapshotState, TurnState};
10
use console::Style;
11
12
use crate::tunnels::shutdown_signal::ShutdownRequest;
13
use crate::util::errors::AnyError;
14
15
use super::agent;
16
use super::args::AgentLogsArgs;
17
use super::output::Styles;
18
use super::CommandContext;
19
20
/// Subscribes to a session and streams actions/notifications in real time.
21
pub async fn agent_logs(ctx: CommandContext, args: AgentLogsArgs) -> Result<i32, AnyError> {
22
let client = agent::connect(&ctx, args.address.as_deref(), args.tunnel.as_deref()).await?;
23
24
let (result, mut sub): (SubscribeResult, _) = {
25
let r: SubscribeResult = agent::request_with_auth(
26
&ctx,
27
&client,
28
"subscribe",
29
SubscribeParams {
30
resource: args.session.clone(),
31
},
32
)
33
.await?;
34
let s = client.attach_subscription(&args.session).await;
35
(r, s)
36
};
37
38
// Print initial state summary.
39
print_initial_state(&args.session, &result);
40
41
let header = Styles::muted();
42
println!(
43
"\n{}",
44
header.apply_to("Streaming events (Ctrl+C to quit)...")
45
);
46
println!("{}", header.apply_to("".repeat(50)));
47
48
// Stream events until Ctrl+C or the subscription closes.
49
let mut shutdown = ShutdownRequest::create_rx([ShutdownRequest::CtrlC]);
50
51
loop {
52
tokio::select! {
53
ev = sub.recv() => match ev {
54
Some(SubscriptionEvent::Action(envelope)) => {
55
print_action(envelope.server_seq, &envelope.action);
56
}
57
Some(SubscriptionEvent::Notification(notif)) => {
58
let notif_style = Style::new().magenta();
59
println!("{}", notif_style.apply_to(format!("notification: {notif:?}")));
60
}
61
None => {
62
println!("{}", Styles::muted().apply_to("Subscription closed."));
63
break;
64
}
65
},
66
_ = shutdown.wait() => {
67
println!("\n{}", Styles::muted().apply_to("Interrupted."));
68
break;
69
}
70
}
71
}
72
73
client.shutdown().await;
74
Ok(0)
75
}
76
77
fn print_initial_state(uri: &str, result: &SubscribeResult) {
78
let title = Styles::title();
79
let label = Styles::label();
80
let uri_style = Styles::uri();
81
82
println!(
83
"\n{} {}",
84
title.apply_to("Session"),
85
uri_style.apply_to(uri)
86
);
87
88
if let SnapshotState::Session(ref session) = result.snapshot.state {
89
let s = &session.summary;
90
if !s.title.is_empty() {
91
println!(" {} {}", label.apply_to("title:"), s.title);
92
}
93
println!(" {} {}", label.apply_to("provider:"), s.provider);
94
if let Some(ref activity) = s.activity {
95
if !activity.is_empty() {
96
println!(" {} {}", label.apply_to("activity:"), activity);
97
}
98
}
99
println!(" {} {}", label.apply_to("turns:"), session.turns.len());
100
101
// Print a brief summary of past turns.
102
for turn in &session.turns {
103
let state_str = match turn.state {
104
TurnState::Complete => Styles::success().apply_to(""),
105
TurnState::Cancelled => Styles::warning().apply_to(""),
106
TurnState::Error => Styles::error().apply_to(""),
107
};
108
let msg = truncate(&turn.user_message.text, 80);
109
println!(" {} {}", state_str, Styles::muted().apply_to(msg));
110
}
111
112
// Print active turn if any.
113
if let Some(ref active) = session.active_turn {
114
let msg = truncate(&active.user_message.text, 80);
115
println!(" {} {}", Style::new().green().bold().apply_to(""), msg);
116
}
117
}
118
119
println!(" {} {}", label.apply_to("seq:"), result.snapshot.from_seq);
120
}
121
122
fn print_action(seq: u64, action: &StateAction) {
123
let seq_str = Styles::muted().apply_to(format!("[{seq:>6}]"));
124
125
// Serialize the action to extract the "type" tag and remaining fields.
126
let value = serde_json::to_value(action).unwrap_or_default();
127
let type_name = value
128
.get("type")
129
.and_then(|v| v.as_str())
130
.unwrap_or("unknown");
131
132
// Build a compact params string from all fields except "type".
133
let params = if let Some(obj) = value.as_object() {
134
let parts: Vec<String> = obj
135
.iter()
136
.filter(|(k, _)| k.as_str() != "type")
137
.map(|(k, v)| {
138
let v_str = match v {
139
serde_json::Value::String(s) => truncate(s, 80),
140
other => truncate(&other.to_string(), 80),
141
};
142
format!("{}={}", Styles::label().apply_to(k), v_str)
143
})
144
.collect();
145
parts.join(" ")
146
} else {
147
String::new()
148
};
149
150
let style = action_style(type_name);
151
println!("{} {} {}", seq_str, style.apply_to(type_name), params);
152
}
153
154
/// Picks a color for the action type name.
155
fn action_style(type_name: &str) -> Style {
156
if type_name.contains("error") || type_name.contains("Failed") {
157
Styles::error()
158
} else if type_name.contains("Complete") || type_name.contains("complete") {
159
Styles::success()
160
} else if type_name.contains("Cancel") || type_name.contains("cancel") {
161
Styles::warning()
162
} else if type_name.contains("oolCall") || type_name.contains("oolcall") {
163
Style::new().blue()
164
} else if type_name.contains("delta")
165
|| type_name.contains("Delta")
166
|| type_name.contains("reasoning")
167
{
168
Styles::muted()
169
} else {
170
Style::new().cyan()
171
}
172
}
173
174
fn truncate(s: &str, max: usize) -> String {
175
let s = s.replace('\n', " ");
176
if s.len() <= max {
177
s
178
} else {
179
format!("{}…", &s[..max - 1])
180
}
181
}
182
183