Repository navigation
Expand file tree
/
Copy pathextension_launch_provider.rs
More file actions
158 lines (141 loc) · 4.99 KB
/
Copy pathextension_launch_provider.rs
File metadata and controls
158 lines (141 loc) · 4.99 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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Connection-level extension launch profile resolution.
use std::sync::{Arc, OnceLock, Weak};
use async_trait::async_trait;
use parking_lot::RwLock;
use serde::Serialize;
use serde_json::Value;
use tracing::warn;
pub use crate::rpc::{
ExtensionLaunchProfile, ExtensionLaunchProviderResolveRequest,
ExtensionLaunchProviderResolveResult,
};
use crate::{
Client, ClientInner, JsonRpcError, JsonRpcRequest, JsonRpcResponse, Result, error_codes,
};
pub(crate) const RESOLVE_METHOD: &str = "extensionLaunchProvider.resolve";
const MISSING_HANDLER_MESSAGE: &str = "No extensionLaunchProvider client-global handler registered";
/// Resolves process launch profiles for extension entrypoints discovered by the runtime.
///
/// Configure an implementation with
/// [`ClientOptions::with_extension_launch_provider`](crate::ClientOptions::with_extension_launch_provider).
/// The SDK registers the provider before [`Client::start`](crate::Client::start)
/// returns, so extension resolution cannot race session creation.
///
/// The returned executable, arguments, and environment are forwarded unchanged.
/// The runtime remains authoritative for its reserved `COPILOT_SDK_PATH`,
/// `SESSION_ID`, and `COPILOT_EXTENSION_PARENT_PID` environment variables.
#[async_trait]
pub trait ExtensionLaunchProvider: Send + Sync + 'static {
/// Resolve a launch profile for one discovered extension entrypoint.
///
/// Return a result with `launch: None` when the provider does not support
/// the entrypoint.
async fn resolve(
&self,
request: ExtensionLaunchProviderResolveRequest,
) -> Result<ExtensionLaunchProviderResolveResult>;
}
pub(crate) struct ExtensionLaunchProviderDispatcher {
handler: RwLock<Option<Arc<dyn ExtensionLaunchProvider>>>,
client: OnceLock<Weak<ClientInner>>,
}
impl ExtensionLaunchProviderDispatcher {
pub(crate) fn new(handler: Option<Arc<dyn ExtensionLaunchProvider>>) -> Self {
Self {
handler: RwLock::new(handler),
client: OnceLock::new(),
}
}
pub(crate) fn set_client(&self, client: Weak<ClientInner>) {
let _ = self.client.set(client);
}
pub(crate) fn is_configured(&self) -> bool {
self.handler.read().is_some()
}
pub(crate) fn clear(&self) {
self.handler.write().take();
}
pub(crate) async fn dispatch(&self, request: JsonRpcRequest) {
let request_id = request.id;
let Some(handler) = self.handler.read().clone() else {
self.send_error(
request_id,
error_codes::INTERNAL_ERROR,
MISSING_HANDLER_MESSAGE,
)
.await;
return;
};
let params = request
.params
.unwrap_or_else(|| Value::Object(serde_json::Map::new()));
let request = match serde_json::from_value(params) {
Ok(request) => request,
Err(error) => {
self.send_error(
request_id,
error_codes::INVALID_PARAMS,
&format!("invalid params: {error}"),
)
.await;
return;
}
};
match handler.resolve(request).await {
Ok(result) => self.respond(request_id, result).await,
Err(error) => {
self.send_error(request_id, error_codes::INTERNAL_ERROR, &error.to_string())
.await;
}
}
}
fn client(&self) -> Option<Client> {
self.client
.get()
.and_then(Weak::upgrade)
.map(Client::from_inner)
}
async fn respond<T: Serialize>(&self, request_id: u64, result: T) {
let value = match serde_json::to_value(result) {
Ok(value) => value,
Err(error) => {
warn!(error = %error, "failed to serialize extension launch provider response");
self.send_error(
request_id,
error_codes::INTERNAL_ERROR,
"serialization failure",
)
.await;
return;
}
};
let Some(client) = self.client() else {
return;
};
let _ = client
.send_response(&JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: request_id,
result: Some(value),
error: None,
})
.await;
}
async fn send_error(&self, request_id: u64, code: i32, message: &str) {
let Some(client) = self.client() else {
return;
};
let _ = client
.send_response(&JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id: request_id,
result: None,
error: Some(JsonRpcError {
code,
message: message.to_string(),
data: None,
}),
})
.await;
}
}