server: multiplex worker protocol subscriptions

This commit is contained in:
2026-08-01 21:07:45 +09:00
parent 72cef5ed9e
commit 11f26a1090
6 changed files with 568 additions and 51 deletions
+61 -1
View File
@@ -248,6 +248,8 @@ pub enum EventSubscriptionSelector {
},
WorkerProtocol {
worker_id: SubscriptionWorkerId,
#[serde(default, skip_serializing_if = "Option::is_none")]
runtime_id: Option<String>,
},
/// Server-derived Workspace projection. Workspace identity comes from the
/// authenticated connection and is deliberately absent from this selector.
@@ -260,7 +262,16 @@ impl EventSubscriptionSelector {
pub fn validate(&self) -> Result<(), SubscriptionValidationError> {
match self {
Self::WorkerLifecycle { worker_ids } => worker_ids.validate(),
Self::WorkerProtocol { worker_id } => worker_id.validate(),
Self::WorkerProtocol {
worker_id,
runtime_id,
} => {
worker_id.validate()?;
if let Some(runtime_id) = runtime_id {
validate_identifier("runtime_id", runtime_id, MAX_RESOURCE_ID_BYTES)?;
}
Ok(())
}
Self::RuntimeWorkers | Self::WorkspaceWorkers | Self::WorkspaceWorkdirs => Ok(()),
}
}
@@ -271,6 +282,7 @@ impl EventSubscriptionSelector {
Self::WorkerLifecycle { worker_ids } => worker_ids.contains(worker_id),
Self::WorkerProtocol {
worker_id: selected,
..
} => selected == worker_id,
Self::WorkspaceWorkdirs => false,
}
@@ -312,6 +324,7 @@ pub enum SubscriptionFramePayload {
Request(SubscriptionRequest),
Response(SubscriptionResponse),
Event(SubscriptionEvent),
WorkerProtocol(SubscriptionWorkerProtocolMethod),
}
impl SubscriptionFramePayload {
@@ -320,10 +333,24 @@ impl SubscriptionFramePayload {
Self::Request(request) => request.validate(),
Self::Response(response) => response.validate(),
Self::Event(event) => event.validate(),
Self::WorkerProtocol(message) => message.validate(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
pub struct SubscriptionWorkerProtocolMethod {
pub subscription_id: SubscriptionId,
pub method: crate::Method,
}
impl SubscriptionWorkerProtocolMethod {
pub fn validate(&self) -> Result<(), SubscriptionValidationError> {
self.subscription_id.validate()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(tag = "method", content = "params", rename_all = "snake_case")]
@@ -617,6 +644,7 @@ impl SubscriptionSnapshot {
(
EventSubscriptionSelector::WorkerProtocol {
worker_id: selected,
..
},
Self::WorkerProtocol { worker_id, .. },
) if selected == worker_id => worker_id.validate(),
@@ -703,6 +731,7 @@ impl SubscriptionEventPayload {
(
EventSubscriptionSelector::WorkerProtocol {
worker_id: selected,
..
},
Self::WorkerProtocol { worker_id, .. },
) if selected == worker_id => Ok(()),
@@ -891,6 +920,37 @@ mod tests {
));
}
#[test]
fn worker_protocol_method_uses_subscription_lane() {
let frame = SubscriptionFrame::new(SubscriptionFramePayload::WorkerProtocol(
SubscriptionWorkerProtocolMethod {
subscription_id: subscription_id(),
method: crate::Method::ListCompletions {
kind: crate::CompletionKind::File,
prefix: "src/".to_string(),
},
},
));
frame.validate().unwrap();
assert_eq!(
serde_json::to_value(frame).unwrap(),
serde_json::json!({
"protocol_version": 1,
"frame": "worker_protocol",
"message": {
"subscription_id": "subscription-1",
"method": {
"method": "list_completions",
"params": {
"kind": "file",
"prefix": "src/"
}
}
}
})
);
}
#[test]
fn workspace_snapshot_allows_equal_local_worker_ids_from_distinct_runtimes() {
let mut first = worker("1");
+3 -1
View File
@@ -12,7 +12,8 @@ use crate::{
SubscriptionFramePayload, SubscriptionId, SubscriptionRejectionCode, SubscriptionRequest,
SubscriptionRequestId, SubscriptionResponse, SubscriptionSnapshot,
SubscriptionTerminationCode, SubscriptionWorkdir, SubscriptionWorkdirId,
SubscriptionWorker, SubscriptionWorkerId, SubscriptionWorkerIds, SubscriptionWorkerState,
SubscriptionWorker, SubscriptionWorkerId, SubscriptionWorkerIds,
SubscriptionWorkerProtocolMethod, SubscriptionWorkerState,
},
};
@@ -71,6 +72,7 @@ pub fn generated_protocol_types() -> String {
push_decl::<SubscriptionRejectionCode>(&cfg, &mut output);
push_decl::<SubscriptionTerminationCode>(&cfg, &mut output);
push_decl::<SubscriptionRequest>(&cfg, &mut output);
push_decl::<SubscriptionWorkerProtocolMethod>(&cfg, &mut output);
push_decl::<SubscriptionResponse>(&cfg, &mut output);
push_decl::<SubscriptionEvent>(&cfg, &mut output);
push_decl::<SubscriptionFramePayload>(&cfg, &mut output);