fix: expose runtime worker list endpoint

This commit is contained in:
Keisuke Hirata 2026-07-21 18:04:14 +09:00
parent f79892baef
commit 2e2d7b7290
No known key found for this signature in database
3 changed files with 84 additions and 3 deletions

View File

@ -22,8 +22,19 @@ pub(crate) async fn run(target: BackendRuntimeListTarget) -> Result<(), Box<dyn
))
})?;
if response.items.is_empty() {
let diagnostics = response
.diagnostics
.iter()
.map(|diagnostic| format!("{}: {}", diagnostic.code, diagnostic.message))
.collect::<Vec<_>>()
.join("; ");
let detail = if diagnostics.is_empty() {
"no backend diagnostics".to_string()
} else {
diagnostics
};
return Err(Box::new(io::Error::other(format!(
"Backend returned no runtime workers for workspace {}",
"Backend returned no runtime workers for workspace {} ({detail})",
response.workspace_id
))));
}

View File

@ -902,6 +902,26 @@ impl RuntimeRegistry {
RuntimeList::new(items, diagnostics)
}
pub fn list_workers_for_runtime(
&self,
runtime_id: &str,
limit: usize,
) -> Result<RuntimeList<WorkerSummary>, RuntimeRegistryError> {
validate_backend_identifier("runtime_id", runtime_id)?;
let runtime = self.runtime(runtime_id)?;
let worker_list = runtime.list_workers(limit);
let mut items: Vec<_> = worker_list
.items
.into_iter()
.filter(|worker| !is_retired_companion_worker(worker))
.take(limit)
.collect();
items.truncate(limit);
let mut diagnostics = worker_list.diagnostics;
diagnostics.truncate(MAX_DIAGNOSTICS);
Ok(RuntimeList::new(items, diagnostics))
}
pub fn list_workers_for_host(
&self,
host_id: &str,
@ -3774,6 +3794,30 @@ mod tests {
assert_eq!(from_runtime_a.label, "worker from runtime a");
}
#[test]
fn registry_worker_list_can_be_scoped_by_runtime_id() {
let registry = RuntimeRegistry::new(vec![
Arc::new(FixtureRuntime::with_worker(
"runtime-a",
"host-a",
"shared-worker",
"worker from runtime a",
)),
Arc::new(FixtureRuntime::with_worker(
"runtime-b",
"host-b",
"shared-worker",
"worker from runtime b",
)),
]);
let listed = registry.list_workers_for_runtime("runtime-b", 10).unwrap();
assert_eq!(listed.items.len(), 1);
assert_eq!(listed.items[0].runtime_id, "runtime-b");
assert_eq!(listed.items[0].host_id, "host-b");
assert_eq!(listed.items[0].label, "worker from runtime b");
}
#[test]
fn registry_worker_lookup_reports_unknown_runtime_and_worker_separately() {
let registry = RuntimeRegistry::new(vec![Arc::new(FixtureRuntime::with_worker(

View File

@ -473,11 +473,11 @@ pub fn build_router(api: WorkspaceApi) -> Router {
)
.route(
"/api/runtimes/{runtime_id}/workers",
post(create_runtime_worker),
get(list_runtime_workers).post(create_runtime_worker),
)
.route(
"/api/w/{workspace_id}/runtimes/{runtime_id}/workers",
post(scoped_create_runtime_worker),
get(scoped_list_runtime_workers).post(scoped_create_runtime_worker),
)
.route(
"/api/runtimes/{runtime_id}/config-bundles",
@ -2305,6 +2305,14 @@ async fn scoped_post_companion_cancel(
post_companion_cancel(State(api), Json(request)).await
}
async fn scoped_list_runtime_workers(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedRuntimePath>,
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
validate_workspace_scope(&api, &path.workspace_id)?;
list_runtime_workers(State(api), AxumPath(path.runtime_id)).await
}
async fn scoped_create_runtime_worker(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedRuntimePath>,
@ -3202,6 +3210,24 @@ fn reject_no_workdir_for_non_embedded_runtime(
))
}
async fn list_runtime_workers(
State(api): State<WorkspaceApi>,
AxumPath(runtime_id): AxumPath<String>,
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
let limit = api.config.max_records.min(200);
let worker_list = api
.runtime
.list_workers_for_runtime(&runtime_id, limit)
.map_err(|err| err.into_error())?;
Ok(Json(RuntimeListResponse {
workspace_id: api.workspace_id().to_string(),
limit,
items: worker_list.items,
source: "runtime_registry".to_string(),
diagnostics: worker_list.diagnostics,
}))
}
async fn create_runtime_worker(
State(api): State<WorkspaceApi>,
AxumPath(runtime_id): AxumPath<String>,