runtime: remove worker transcript projection

This commit is contained in:
2026-07-11 03:55:44 +09:00
parent 210f41e020
commit 156f8ad044
20 changed files with 428 additions and 1179 deletions
+16 -215
View File
@@ -19,9 +19,10 @@ use worker_runtime::catalog::{
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest,
WorkingDirectoryStatus, WorkingDirectorySummary,
};
use worker_runtime::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary};
#[cfg(test)]
use worker_runtime::config_bundle::{
ConfigBundle, ConfigBundleAvailability, ConfigBundleMetadata, ConfigBundleProvenance,
ConfigBundleSummary, ConfigProfileDescriptor,
ConfigBundleMetadata, ConfigBundleProvenance, ConfigProfileDescriptor,
};
use worker_runtime::error::RuntimeError as EmbeddedRuntimeError;
use worker_runtime::execution::{
@@ -30,10 +31,10 @@ use worker_runtime::execution::{
use worker_runtime::fs_store::FsRuntimeStoreOptions;
use worker_runtime::http_server::{
RuntimeHttpConfigBundleAvailabilityResponse, RuntimeHttpConfigBundleSyncRequest,
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpTranscriptResponse,
RuntimeHttpWorkerInputResponse, RuntimeHttpWorkerLifecycleRequest,
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse,
RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse,
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpWorkerInputResponse,
RuntimeHttpWorkerLifecycleRequest, RuntimeHttpWorkerLifecycleResponse,
RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse, RuntimeHttpWorkingDirectoriesResponse,
RuntimeHttpWorkingDirectoryResponse,
};
use worker_runtime::identity::{
RuntimeId as EmbeddedRuntimeId, WorkerId as EmbeddedWorkerId, WorkerRef as EmbeddedWorkerRef,
@@ -42,9 +43,6 @@ use worker_runtime::interaction::{
WorkerInput as EmbeddedWorkerInput, WorkerInputKind as EmbeddedWorkerInputKind,
};
use worker_runtime::management::{RuntimeOptions as EmbeddedRuntimeOptions, RuntimeStatus};
use worker_runtime::observation::{
TranscriptProjection as EmbeddedTranscriptProjection, TranscriptQuery, TranscriptRole,
};
use worker_runtime::profile_archive::{ProfileSourceArchive, ProfileSourceArchiveInput};
const EMBEDDED_RUNTIME_ID: &str = "embedded-worker-runtime";
@@ -454,34 +452,10 @@ pub struct WorkerInputResult {
pub runtime_id: String,
pub worker_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub transcript_sequence: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_id: Option<u64>,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerTranscriptItem {
pub sequence: u64,
pub role: String,
pub content: String,
pub event_id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerTranscriptProjection {
pub state: WorkerOperationState,
pub runtime_id: String,
pub worker_id: String,
pub start: usize,
pub limit: usize,
pub total_items: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_start: Option<usize>,
pub items: Vec<WorkerTranscriptItem>,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerProxyConnectPoint {
pub kind: String,
@@ -705,7 +679,6 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(),
worker_id: worker_id.to_string(),
transcript_sequence: None,
event_id: None,
diagnostics: vec![diagnostic(
"worker_input_pending",
@@ -717,31 +690,6 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
}
}
fn transcript(
&self,
worker_id: &str,
start: usize,
limit: usize,
) -> WorkerTranscriptProjection {
WorkerTranscriptProjection {
state: WorkerOperationState::Unsupported,
runtime_id: self.runtime_id().to_string(),
worker_id: worker_id.to_string(),
start,
limit,
total_items: 0,
next_start: None,
items: Vec::new(),
diagnostics: vec![diagnostic(
"worker_transcript_pending",
DiagnosticSeverity::Info,
format!(
"bounded transcript for '{worker_id}' is not implemented by this registry source"
),
)],
}
}
fn proxy_connect_points(&self, worker_id: &str) -> Vec<WorkerProxyConnectPoint> {
vec![WorkerProxyConnectPoint {
kind: "stream_proxy".to_string(),
@@ -1050,27 +998,6 @@ impl RuntimeRegistry {
Ok(runtime.send_input(worker_id, request))
}
pub fn transcript(
&self,
runtime_id: &str,
worker_id: &str,
start: usize,
limit: usize,
) -> Result<WorkerTranscriptProjection, 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.transcript(worker_id, start, limit))
}
pub fn stop_worker(
&self,
runtime_id: &str,
@@ -1781,7 +1708,6 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(),
transcript_sequence: Some(ack.transcript_sequence),
event_id: Some(ack.event_id),
diagnostics: Vec::new(),
},
@@ -1792,42 +1718,6 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
),
}
}
fn transcript(
&self,
worker_id: &str,
start: usize,
limit: usize,
) -> WorkerTranscriptProjection {
let Some(worker_ref) = self.worker_ref(worker_id) else {
return embedded_transcript_rejected(
&self.runtime_id,
worker_id,
start,
limit,
diagnostic(
"embedded_worker_id_invalid",
DiagnosticSeverity::Warning,
"Worker id was empty and cannot be resolved".to_string(),
),
);
};
match self
.runtime
.transcript_projection(&worker_ref, TranscriptQuery::new(start, limit))
{
Ok(projection) => {
embedded_transcript_projection(&self.runtime_id, worker_id, projection)
}
Err(err) => embedded_transcript_rejected(
&self.runtime_id,
worker_id,
start,
limit,
embedded_runtime_diagnostic(&err),
),
}
}
}
#[derive(Clone)]
@@ -2444,31 +2334,12 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
state: WorkerOperationState::Accepted,
runtime_id: self.runtime_id.clone(),
worker_id: worker_id.to_string(),
transcript_sequence: Some(response.ack.transcript_sequence),
event_id: Some(response.ack.event_id),
diagnostics: Vec::new(),
},
Err(diagnostic) => remote_input_rejected(&self.runtime_id, worker_id, diagnostic),
}
}
fn transcript(
&self,
worker_id: &str,
start: usize,
limit: usize,
) -> WorkerTranscriptProjection {
match self.get_json::<RuntimeHttpTranscriptResponse>(&format!(
"/v1/workers/{worker_id}/transcript?start={start}&limit={limit}"
)) {
Ok(response) => {
embedded_transcript_projection(&self.runtime_id, worker_id, response.transcript)
}
Err(diagnostic) => {
embedded_transcript_rejected(&self.runtime_id, worker_id, start, limit, diagnostic)
}
}
}
}
fn embedded_runtime_capabilities(
@@ -2676,12 +2547,14 @@ fn default_profile_source_archive_http_source(
})
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ProfileSourceArchiveTransport {
Inline,
BackendResourceHandle,
}
#[cfg(test)]
fn default_embedded_config_bundle(
profile: &ProfileSelector,
workspace_id: &str,
@@ -2844,7 +2717,6 @@ fn embedded_input_rejected(
state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(),
transcript_sequence: None,
event_id: None,
diagnostics: vec![diagnostic],
}
@@ -2859,7 +2731,6 @@ fn remote_input_rejected(
state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(),
transcript_sequence: None,
event_id: None,
diagnostics: vec![diagnostic],
}
@@ -2893,61 +2764,6 @@ fn remote_lifecycle_rejected(
}
}
fn embedded_transcript_projection(
runtime_id: &str,
worker_id: &str,
projection: EmbeddedTranscriptProjection,
) -> WorkerTranscriptProjection {
WorkerTranscriptProjection {
state: WorkerOperationState::Accepted,
runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(),
start: projection.start,
limit: projection.limit,
total_items: projection.total_items,
next_start: projection.next_start,
items: projection
.items
.into_iter()
.map(|item| WorkerTranscriptItem {
sequence: item.sequence,
role: embedded_transcript_role_label(item.role).to_string(),
content: item.content,
event_id: item.event_id,
})
.collect(),
diagnostics: Vec::new(),
}
}
fn embedded_transcript_rejected(
runtime_id: &str,
worker_id: &str,
start: usize,
limit: usize,
diagnostic: RuntimeDiagnostic,
) -> WorkerTranscriptProjection {
WorkerTranscriptProjection {
state: WorkerOperationState::Rejected,
runtime_id: runtime_id.to_string(),
worker_id: worker_id.to_string(),
start,
limit,
total_items: 0,
next_start: None,
items: Vec::new(),
diagnostics: vec![diagnostic],
}
}
fn embedded_transcript_role_label(role: TranscriptRole) -> &'static str {
match role {
TranscriptRole::User => "user",
TranscriptRole::Assistant => "assistant",
TranscriptRole::System => "system",
}
}
fn embedded_runtime_diagnostic(error: &EmbeddedRuntimeError) -> RuntimeDiagnostic {
match error {
EmbeddedRuntimeError::RuntimeStopped { .. } => diagnostic(
@@ -3814,7 +3630,7 @@ mod tests {
}
#[test]
fn embedded_runtime_with_execution_backend_routes_input_and_projects_transcript() {
fn embedded_runtime_with_execution_backend_routes_input_and_updates_status() {
let runtime = EmbeddedWorkerRuntime::new_memory_with_execution_backend(
"local:test",
Arc::new(AcceptingExecutionBackend::default()),
@@ -3841,13 +3657,7 @@ mod tests {
.worker(&worker.worker_id)
.worker
.expect("worker detail");
let transcript = runtime.transcript(&worker.worker_id, 0, 10);
if detail.status == "idle"
&& transcript
.items
.iter()
.any(|entry| entry.role == "assistant" && entry.content == "echo: hello")
{
if detail.status == "idle" {
assert!(detail.capabilities.can_accept_input);
break;
}
@@ -3860,7 +3670,7 @@ mod tests {
}
#[test]
fn embedded_runtime_registers_routes_input_and_transcript_without_internal_leaks() {
fn embedded_runtime_registers_routes_input_without_internal_leaks() {
let registry = RuntimeRegistry::for_workspace(
EmbeddedWorkerRuntime::new_memory_with_execution_backend(
"local:test",
@@ -3933,18 +3743,11 @@ mod tests {
assert_eq!(input.runtime_id, EMBEDDED_RUNTIME_ID);
assert_eq!(input.worker_id, worker.worker_id);
let transcript = registry
.transcript(EMBEDDED_RUNTIME_ID, &worker.worker_id, 0, 10)
let detail = registry
.worker(EMBEDDED_RUNTIME_ID, &worker.worker_id)
.unwrap();
assert_eq!(transcript.state, WorkerOperationState::Accepted);
assert!(
transcript
.items
.iter()
.any(|entry| entry.role == "user" && entry.content == "hello embedded runtime")
);
let json = serde_json::to_string(&(embedded_summary, worker, transcript)).unwrap();
let json = serde_json::to_string(&(embedded_summary, worker, input, detail)).unwrap();
for forbidden in [
"/workspace/project",
"metadata.json",
@@ -3953,6 +3756,7 @@ mod tests {
"token",
"credential",
"provider",
"transcript",
"can_stream_events",
"can_read_bounded_transcript",
] {
@@ -4093,7 +3897,6 @@ mod tests {
"ack": {
"worker_ref": { "runtime_id": "remote:primary", "worker_id": "worker-remote-1" },
"status": "running",
"transcript_sequence": 7,
"event_id": 8
}
})
@@ -4157,7 +3960,6 @@ mod tests {
)
.unwrap();
assert_eq!(input.state, WorkerOperationState::Accepted);
assert_eq!(input.transcript_sequence, Some(7));
assert_eq!(input.event_id, Some(8));
server.join().expect("mock remote server finished");
@@ -4482,7 +4284,6 @@ mod tests {
"source_graph": { "source_count": 0, "total_source_bytes": 0, "entrypoints": {}, "import_count": 0 }
},
"config_bundle": { "id": "remote-bundle", "digest": "remote-digest" },
"transcript_len": 0,
"last_event_id": 0
})
}
+22 -79
View File
@@ -27,7 +27,7 @@ use crate::hosts::{
RuntimeRegistryUnregisterResult, RuntimeSummary, WorkerInputRequest, WorkerInputResult,
WorkerLifecycleRequest, WorkerLifecycleResult, WorkerOperationState,
WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult,
WorkerSpawnWorkingDirectoryRequest, WorkerSummary, WorkerTranscriptProjection,
WorkerSpawnWorkingDirectoryRequest, WorkerSummary,
};
use crate::identity::WorkspaceIdentity;
use crate::observation::{
@@ -492,14 +492,6 @@ pub fn build_router(api: WorkspaceApi) -> Router {
"/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/cancel",
post(scoped_cancel_runtime_worker),
)
.route(
"/api/runtimes/{runtime_id}/workers/{worker_id}/transcript",
get(get_runtime_worker_transcript),
)
.route(
"/api/w/{workspace_id}/runtimes/{runtime_id}/workers/{worker_id}/transcript",
get(scoped_get_runtime_worker_transcript),
)
.route(
"/api/runtimes/{runtime_id}/workers/{worker_id}/events/ws",
get(worker_observation_ws),
@@ -1498,20 +1490,6 @@ async fn scoped_cancel_runtime_worker(
.await
}
async fn scoped_get_runtime_worker_transcript(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
Query(query): Query<TranscriptQuery>,
) -> ApiResult<Json<WorkerTranscriptProjection>> {
validate_workspace_scope(&api, &path.workspace_id)?;
get_runtime_worker_transcript(
State(api),
AxumPath((path.runtime_id, path.worker_id)),
Query(query),
)
.await
}
async fn scoped_worker_observation_ws(
ws: WebSocketUpgrade,
State(api): State<WorkspaceApi>,
@@ -2245,20 +2223,6 @@ async fn cancel_runtime_worker(
Ok(Json(result))
}
async fn get_runtime_worker_transcript(
State(api): State<WorkspaceApi>,
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
Query(query): Query<TranscriptQuery>,
) -> ApiResult<Json<WorkerTranscriptProjection>> {
let limit = query.limit.unwrap_or(api.config.max_records).min(200);
let start = query.start.unwrap_or(0);
let result = api
.runtime
.transcript(&runtime_id, &worker_id, start, limit)
.map_err(|err| err.into_error())?;
Ok(Json(result))
}
async fn worker_observation_ws(
State(api): State<WorkspaceApi>,
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
@@ -5243,8 +5207,7 @@ mod tests {
}
#[tokio::test]
async fn embedded_runtime_fs_store_restores_catalog_config_bundle_transcript_and_stale_execution()
{
async fn embedded_runtime_fs_store_restores_catalog_config_bundle_and_stale_execution() {
let dir = tempfile::tempdir().unwrap();
let config = test_server_config(dir.path().join("workspace"));
let store_root = config.embedded_runtime_store_root.clone();
@@ -5304,24 +5267,16 @@ mod tests {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
loop {
let transcript = api
let detail = api
.runtime
.transcript("embedded-worker-runtime", &worker_id, 0, 10)
.expect("transcript");
if transcript.items.iter().any(|item| {
item.role == "assistant" && item.content == "server companion echoed: persist me"
}) {
assert!(
transcript
.items
.iter()
.any(|item| item.role == "user" && item.content == "persist me")
);
.worker("embedded-worker-runtime", &worker_id)
.expect("worker detail");
if detail.status == "idle" {
break;
}
assert!(
std::time::Instant::now() < deadline,
"timed out waiting for deterministic transcript"
"timed out waiting for deterministic worker completion"
);
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
@@ -5359,20 +5314,6 @@ mod tests {
.any(|summary| summary.id == bundle_id)
);
let restored_transcript = restored
.runtime
.transcript("embedded-worker-runtime", &worker_id, 0, 10)
.expect("restored transcript");
assert!(
restored_transcript
.items
.iter()
.any(|item| item.role == "user" && item.content == "persist me")
);
assert!(restored_transcript.items.iter().any(|item| {
item.role == "assistant" && item.content == "server companion echoed: persist me"
}));
let rejected_input = restored
.runtime
.send_input(
@@ -5591,15 +5532,20 @@ mod tests {
assert_eq!(accepted["worker_id"], worker_id);
assert!(accepted["diagnostics"].as_array().unwrap().is_empty());
let transcript = get_json(
app.clone(),
&format!("/api/runtimes/embedded-worker-runtime/workers/{worker_id}/transcript?start=0&limit=10"),
)
.await;
assert_eq!(transcript["state"], "accepted");
assert!(transcript["items"].as_array().unwrap().iter().any(
|item| item["role"] == "user" && item["content"] == "hello from browser-facing api"
));
let transcript_route = app
.clone()
.oneshot(
Request::builder()
.method("GET")
.uri(format!(
"/api/runtimes/embedded-worker-runtime/workers/{worker_id}/transcript?start=0&limit=10"
))
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(transcript_route.status(), StatusCode::NOT_FOUND);
let wrong_runtime = app
.clone()
@@ -5623,10 +5569,7 @@ mod tests {
.unwrap();
assert_eq!(wrong_runtime.status(), StatusCode::NOT_FOUND);
let projected = format!(
"{}{}{}{}{}",
embedded_summary, spawned, worker, accepted, transcript
);
let projected = format!("{}{}{}{}", embedded_summary, spawned, worker, accepted);
for forbidden in [
dir.path().to_string_lossy().as_ref(),
"metadata.json",