feat: add web console commands
This commit is contained in:
@@ -31,7 +31,8 @@ use worker_runtime::execution::{
|
||||
use worker_runtime::fs_store::FsRuntimeStoreOptions;
|
||||
use worker_runtime::http_server::{
|
||||
RuntimeHttpConfigBundleAvailabilityResponse, RuntimeHttpConfigBundleSyncRequest,
|
||||
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpWorkerDeleteResponse,
|
||||
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpWorkerCompletionsRequest,
|
||||
RuntimeHttpWorkerCompletionsResponse, RuntimeHttpWorkerDeleteResponse,
|
||||
RuntimeHttpWorkerInputResponse, RuntimeHttpWorkerLifecycleRequest,
|
||||
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse,
|
||||
RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse,
|
||||
@@ -436,6 +437,9 @@ pub struct WorkerLifecycleResult {
|
||||
pub enum WorkerInputKind {
|
||||
User,
|
||||
System,
|
||||
Compact,
|
||||
ListRewindTargets,
|
||||
RegisterPeer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -452,6 +456,25 @@ pub struct WorkerInputRequest {
|
||||
#[serde(default = "default_worker_input_kind")]
|
||||
pub kind: WorkerInputKind,
|
||||
pub content: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub segments: Option<Vec<protocol::Segment>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct WorkerCompletionsRequest {
|
||||
pub kind: protocol::CompletionKind,
|
||||
#[serde(default)]
|
||||
pub prefix: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct WorkerCompletionsResult {
|
||||
pub runtime_id: String,
|
||||
pub worker_id: String,
|
||||
pub kind: protocol::CompletionKind,
|
||||
pub prefix: String,
|
||||
pub entries: Vec<protocol::CompletionEntry>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -712,6 +735,25 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_completions(
|
||||
&self,
|
||||
worker_id: &str,
|
||||
request: WorkerCompletionsRequest,
|
||||
) -> WorkerCompletionsResult {
|
||||
WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id().to_string(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![diagnostic(
|
||||
"worker_completions_unsupported",
|
||||
DiagnosticSeverity::Info,
|
||||
format!("runtime does not implement completions for worker '{worker_id}'"),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
fn proxy_connect_points(&self, worker_id: &str) -> Vec<WorkerProxyConnectPoint> {
|
||||
vec![WorkerProxyConnectPoint {
|
||||
kind: "stream_proxy".to_string(),
|
||||
@@ -1020,6 +1062,26 @@ impl RuntimeRegistry {
|
||||
Ok(runtime.send_input(worker_id, request))
|
||||
}
|
||||
|
||||
pub fn worker_completions(
|
||||
&self,
|
||||
runtime_id: &str,
|
||||
worker_id: &str,
|
||||
request: WorkerCompletionsRequest,
|
||||
) -> Result<WorkerCompletionsResult, RuntimeRegistryError> {
|
||||
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||
validate_backend_identifier("worker_id", worker_id)?;
|
||||
let runtime = self.runtime(runtime_id)?;
|
||||
let lookup = runtime.worker(worker_id);
|
||||
if lookup.worker.is_none() {
|
||||
return Err(operation_failed_or_unknown_worker(
|
||||
runtime_id,
|
||||
worker_id,
|
||||
lookup.diagnostics,
|
||||
));
|
||||
}
|
||||
Ok(runtime.worker_completions(worker_id, request))
|
||||
}
|
||||
|
||||
pub fn stop_worker(
|
||||
&self,
|
||||
runtime_id: &str,
|
||||
@@ -1750,8 +1812,12 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||
kind: match request.kind {
|
||||
WorkerInputKind::User => EmbeddedWorkerInputKind::User,
|
||||
WorkerInputKind::System => EmbeddedWorkerInputKind::System,
|
||||
WorkerInputKind::Compact => EmbeddedWorkerInputKind::Compact,
|
||||
WorkerInputKind::ListRewindTargets => EmbeddedWorkerInputKind::ListRewindTargets,
|
||||
WorkerInputKind::RegisterPeer => EmbeddedWorkerInputKind::RegisterPeer,
|
||||
},
|
||||
content: request.content,
|
||||
segments: request.segments,
|
||||
};
|
||||
match self.runtime.send_input(&worker_ref, input) {
|
||||
Ok(ack) => WorkerInputResult {
|
||||
@@ -1768,6 +1834,64 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_completions(
|
||||
&self,
|
||||
worker_id: &str,
|
||||
request: WorkerCompletionsRequest,
|
||||
) -> WorkerCompletionsResult {
|
||||
if !self.execution_enabled {
|
||||
return WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id.clone(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![diagnostic(
|
||||
"embedded_worker_execution_unavailable",
|
||||
DiagnosticSeverity::Info,
|
||||
format!(
|
||||
"worker completions for '{worker_id}' require an embedded execution backend"
|
||||
),
|
||||
)],
|
||||
};
|
||||
}
|
||||
let Some(worker_ref) = self.worker_ref(worker_id) else {
|
||||
return WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id.clone(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![diagnostic(
|
||||
"embedded_worker_id_invalid",
|
||||
DiagnosticSeverity::Warning,
|
||||
"Worker id was empty and cannot be resolved".to_string(),
|
||||
)],
|
||||
};
|
||||
};
|
||||
match self
|
||||
.runtime
|
||||
.worker_completions(&worker_ref, request.kind, &request.prefix)
|
||||
{
|
||||
Ok(entries) => WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id.clone(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
entries,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(error) => WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id.clone(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![embedded_runtime_diagnostic(&error)],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -2397,8 +2521,12 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||
kind: match request.kind {
|
||||
WorkerInputKind::User => EmbeddedWorkerInputKind::User,
|
||||
WorkerInputKind::System => EmbeddedWorkerInputKind::System,
|
||||
WorkerInputKind::Compact => EmbeddedWorkerInputKind::Compact,
|
||||
WorkerInputKind::ListRewindTargets => EmbeddedWorkerInputKind::ListRewindTargets,
|
||||
WorkerInputKind::RegisterPeer => EmbeddedWorkerInputKind::RegisterPeer,
|
||||
},
|
||||
content: request.content,
|
||||
segments: request.segments,
|
||||
};
|
||||
match self.post_json::<_, RuntimeHttpWorkerInputResponse>(
|
||||
&format!("/v1/workers/{worker_id}/input"),
|
||||
@@ -2414,6 +2542,38 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||
Err(diagnostic) => remote_input_rejected(&self.runtime_id, worker_id, diagnostic),
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_completions(
|
||||
&self,
|
||||
worker_id: &str,
|
||||
request: WorkerCompletionsRequest,
|
||||
) -> WorkerCompletionsResult {
|
||||
let request = RuntimeHttpWorkerCompletionsRequest {
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
};
|
||||
match self.post_json::<_, RuntimeHttpWorkerCompletionsResponse>(
|
||||
&format!("/v1/workers/{worker_id}/completions"),
|
||||
&request,
|
||||
) {
|
||||
Ok(response) => WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id.clone(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: response.kind,
|
||||
prefix: response.prefix,
|
||||
entries: response.entries,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(diagnostic) => WorkerCompletionsResult {
|
||||
runtime_id: self.runtime_id.clone(),
|
||||
worker_id: worker_id.to_string(),
|
||||
kind: request.kind,
|
||||
prefix: request.prefix,
|
||||
entries: Vec::new(),
|
||||
diagnostics: vec![diagnostic],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn embedded_runtime_capabilities(
|
||||
@@ -3676,6 +3836,7 @@ mod tests {
|
||||
request.initial_input = Some(EmbeddedWorkerInput {
|
||||
kind: EmbeddedWorkerInputKind::System,
|
||||
content: "system/role instruction belongs in profile".to_string(),
|
||||
segments: None,
|
||||
});
|
||||
|
||||
let spawned = runtime.spawn_worker(request);
|
||||
@@ -3707,6 +3868,7 @@ mod tests {
|
||||
WorkerInputRequest {
|
||||
kind: WorkerInputKind::User,
|
||||
content: "hello".to_string(),
|
||||
segments: None,
|
||||
},
|
||||
);
|
||||
assert_eq!(input.state, WorkerOperationState::Accepted);
|
||||
@@ -3792,6 +3954,7 @@ mod tests {
|
||||
WorkerInputRequest {
|
||||
kind: WorkerInputKind::User,
|
||||
content: "hello embedded runtime".to_string(),
|
||||
segments: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -4007,6 +4170,7 @@ mod tests {
|
||||
WorkerInputRequest {
|
||||
kind: WorkerInputKind::User,
|
||||
content: "hello remote".to_string(),
|
||||
segments: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -28,10 +28,10 @@ use crate::hosts::{
|
||||
ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EmbeddedWorkerRuntime,
|
||||
HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry,
|
||||
RuntimeRegistryError, RuntimeRegistryUnregisterResult, RuntimeSummary, WorkerCapabilitySummary,
|
||||
WorkerImplementationSummary, WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest,
|
||||
WorkerLifecycleResult, WorkerOperationState, WorkerSpawnAcceptanceRequirement,
|
||||
WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult, WorkerSpawnWorkingDirectoryRequest,
|
||||
WorkerSummary, WorkerWorkspaceSummary,
|
||||
WorkerCompletionsRequest, WorkerCompletionsResult, WorkerImplementationSummary,
|
||||
WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest, WorkerLifecycleResult,
|
||||
WorkerOperationState, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest,
|
||||
WorkerSpawnResult, WorkerSpawnWorkingDirectoryRequest, WorkerSummary, WorkerWorkspaceSummary,
|
||||
};
|
||||
use crate::identity::WorkspaceIdentity;
|
||||
use crate::observation::{
|
||||
@@ -496,6 +496,14 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||
"/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/input",
|
||||
post(scoped_send_runtime_worker_input),
|
||||
)
|
||||
.route(
|
||||
"/api/runtimes/{runtime_id}/workers/{worker_id}/completions",
|
||||
post(runtime_worker_completions),
|
||||
)
|
||||
.route(
|
||||
"/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/completions",
|
||||
post(scoped_runtime_worker_completions),
|
||||
)
|
||||
.route(
|
||||
"/api/runtimes/{runtime_id}/workers/{worker_id}/stop",
|
||||
post(stop_runtime_worker),
|
||||
@@ -2252,6 +2260,20 @@ async fn scoped_send_runtime_worker_input(
|
||||
.await
|
||||
}
|
||||
|
||||
async fn scoped_runtime_worker_completions(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
|
||||
Json(request): Json<WorkerCompletionsRequest>,
|
||||
) -> ApiResult<Json<WorkerCompletionsResult>> {
|
||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||
runtime_worker_completions(
|
||||
State(api),
|
||||
AxumPath((path.runtime_id, path.worker_id)),
|
||||
Json(request),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn scoped_stop_runtime_worker(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
|
||||
@@ -2786,6 +2808,7 @@ async fn create_workspace_worker(
|
||||
Some(EmbeddedWorkerInput {
|
||||
kind: EmbeddedWorkerInputKind::User,
|
||||
content: initial_text,
|
||||
segments: None,
|
||||
})
|
||||
};
|
||||
let selected_working_directory_id = request
|
||||
@@ -3137,6 +3160,18 @@ async fn send_runtime_worker_input(
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
async fn runtime_worker_completions(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
|
||||
Json(request): Json<WorkerCompletionsRequest>,
|
||||
) -> ApiResult<Json<WorkerCompletionsResult>> {
|
||||
let result = api
|
||||
.runtime
|
||||
.worker_completions(&runtime_id, &worker_id, request)
|
||||
.map_err(|err| err.into_error())?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
async fn stop_runtime_worker(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
|
||||
@@ -7237,6 +7272,7 @@ mod tests {
|
||||
WorkerInputRequest {
|
||||
kind: WorkerInputKind::User,
|
||||
content: "persist me".to_string(),
|
||||
segments: None,
|
||||
},
|
||||
)
|
||||
.expect("send input");
|
||||
@@ -7298,6 +7334,7 @@ mod tests {
|
||||
WorkerInputRequest {
|
||||
kind: WorkerInputKind::User,
|
||||
content: "should not be routed to stale handle".to_string(),
|
||||
segments: None,
|
||||
},
|
||||
)
|
||||
.expect("stale worker input is projected as an operation result");
|
||||
|
||||
Reference in New Issue
Block a user