feat: page console history across compaction
This commit is contained in:
@@ -477,6 +477,13 @@ pub struct WorkerSessionObservationRequest {
|
||||
pub worker_ref: WorkerRef,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct WorkerSessionHistoryRequest {
|
||||
pub worker_ref: WorkerRef,
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
pub trait WorkerExecutionBackend: Send + Sync + 'static {
|
||||
fn backend_id(&self) -> &str;
|
||||
|
||||
@@ -497,6 +504,16 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_session_history(
|
||||
&self,
|
||||
_request: WorkerSessionHistoryRequest,
|
||||
) -> runtime_api::WorkerSessionHistoryAvailability {
|
||||
runtime_api::WorkerSessionHistoryAvailability::Unavailable {
|
||||
reason: runtime_api::WorkerSessionHistoryUnavailableReason::Unsupported,
|
||||
message: "session history paging is not supported by this Runtime".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult;
|
||||
|
||||
/// Read-only restore validation performed before any live controller,
|
||||
@@ -726,6 +743,13 @@ impl WorkerExecutionBackendRef {
|
||||
self.backend.worker_session(request)
|
||||
}
|
||||
|
||||
pub(crate) fn worker_session_history(
|
||||
&self,
|
||||
request: WorkerSessionHistoryRequest,
|
||||
) -> runtime_api::WorkerSessionHistoryAvailability {
|
||||
self.backend.worker_session_history(request)
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_worker(
|
||||
&self,
|
||||
request: WorkerExecutionSpawnRequest,
|
||||
|
||||
@@ -260,6 +260,35 @@ impl runtime_api::RuntimeApi for RuntimeManagementApi {
|
||||
.map_err(api_error)
|
||||
}
|
||||
|
||||
async fn worker_session_history(
|
||||
&self,
|
||||
worker_id: String,
|
||||
request: runtime_api::WorkerSessionHistoryRequest,
|
||||
) -> Result<runtime_api::WorkerSessionHistoryAvailability, runtime_api::RuntimeApiError> {
|
||||
let worker_ref = worker_ref_for(&self.state.runtime, worker_id).map_err(api_error)?;
|
||||
let scope = auth_workspace_scope(&self.state, auth_extension().as_ref())
|
||||
.map_err(api_error)?
|
||||
.ok_or_else(|| {
|
||||
runtime_api::RuntimeApiError::new(
|
||||
StatusCode::FORBIDDEN.as_u16(),
|
||||
"runtime_worker_session_scope_required",
|
||||
"Worker Session history requires a Workspace-scoped capability",
|
||||
)
|
||||
})?;
|
||||
if scope.workspace_id != request.workspace_id {
|
||||
return Err(runtime_api::RuntimeApiError::new(
|
||||
StatusCode::FORBIDDEN.as_u16(),
|
||||
"runtime_worker_session_workspace_scope_mismatch",
|
||||
"Worker Session Workspace scope does not match the authenticated capability",
|
||||
));
|
||||
}
|
||||
self.state
|
||||
.runtime
|
||||
.worker_session_history_scoped(&scope, &worker_ref, request.cursor, request.limit)
|
||||
.map_err(RuntimeHttpRestError::runtime)
|
||||
.map_err(api_error)
|
||||
}
|
||||
|
||||
async fn create_worker(
|
||||
&self,
|
||||
value: runtime_api::CreateWorkerRequest,
|
||||
|
||||
@@ -16,7 +16,8 @@ use crate::execution::{
|
||||
WorkerExecutionBackend, WorkerExecutionBackendRef, WorkerExecutionHandle,
|
||||
WorkerExecutionOperation, WorkerExecutionResult, WorkerExecutionSpawnRequest,
|
||||
WorkerExecutionSpawnResult, WorkerExecutionStopRequest, WorkerLifecycleOperationId,
|
||||
WorkerSessionObservationRequest, WorkspaceConfigFetchRequest, WorkspaceConfigFetchResult,
|
||||
WorkerSessionHistoryRequest, WorkerSessionObservationRequest, WorkspaceConfigFetchRequest,
|
||||
WorkspaceConfigFetchResult,
|
||||
};
|
||||
#[cfg(feature = "fs-store")]
|
||||
use crate::fs_store::{
|
||||
@@ -1613,6 +1614,38 @@ impl Runtime {
|
||||
})
|
||||
}
|
||||
|
||||
/// Read one bounded page from the adopted durable Session lineage without
|
||||
/// restoring the Worker or modifying model context.
|
||||
pub fn worker_session_history_scoped(
|
||||
&self,
|
||||
scope: &RuntimeWorkspaceScope,
|
||||
worker_ref: &WorkerRef,
|
||||
cursor: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<runtime_api::WorkerSessionHistoryAvailability, RuntimeError> {
|
||||
let operation_lock = self.worker_operation_lock(worker_ref.worker_id)?;
|
||||
let _operation_guard = operation_lock
|
||||
.lock()
|
||||
.map_err(|_| RuntimeError::StatePoisoned)?;
|
||||
self.ensure_worker_in_workspace(scope, worker_ref)?;
|
||||
let backend = {
|
||||
let state = self.lock()?;
|
||||
state.worker(worker_ref)?;
|
||||
state.execution_backend.clone()
|
||||
};
|
||||
Ok(match backend {
|
||||
Some(backend) => backend.worker_session_history(WorkerSessionHistoryRequest {
|
||||
worker_ref: worker_ref.clone(),
|
||||
cursor,
|
||||
limit,
|
||||
}),
|
||||
None => runtime_api::WorkerSessionHistoryAvailability::Unavailable {
|
||||
reason: runtime_api::WorkerSessionHistoryUnavailableReason::Unsupported,
|
||||
message: "session history paging is not supported by this Runtime".to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Attach a live execution through a workspace-scoped Runtime authorization context.
|
||||
pub fn restore_worker_scoped(
|
||||
&self,
|
||||
|
||||
@@ -172,6 +172,15 @@ pub trait RuntimeWorkerFactory: Send + Sync + 'static {
|
||||
Err(session_store::RetainedSnapshotReadError::RetentionMissing)
|
||||
}
|
||||
|
||||
fn retained_session_history_page(
|
||||
&self,
|
||||
_worker_ref: &WorkerRef,
|
||||
_cursor: Option<&str>,
|
||||
_limit: Option<usize>,
|
||||
) -> Result<protocol::SessionHistoryPage, session_store::RetainedHistoryReadError> {
|
||||
Err(session_store::RetainedHistoryReadError::RetentionMissing)
|
||||
}
|
||||
|
||||
async fn spawn_controller(
|
||||
&self,
|
||||
request: WorkerExecutionSpawnRequest,
|
||||
@@ -1027,6 +1036,25 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||
)
|
||||
}
|
||||
|
||||
fn retained_session_history_page(
|
||||
&self,
|
||||
worker_ref: &WorkerRef,
|
||||
cursor: Option<&str>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<protocol::SessionHistoryPage, session_store::RetainedHistoryReadError> {
|
||||
let aggregate_dir = self
|
||||
.worker_aggregate_dir(worker_ref)
|
||||
.map_err(|_| session_store::RetainedHistoryReadError::RetentionMissing)?;
|
||||
let worker_name = Self::runtime_worker_name_for_ref(worker_ref);
|
||||
session_store::read_retained_session_history_page(
|
||||
&aggregate_dir,
|
||||
&worker_name,
|
||||
cursor,
|
||||
limit,
|
||||
session_store::RetainedHistoryReadLimits::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn observe_workspace_prompt_projection(
|
||||
&self,
|
||||
projection: worker::WorkspacePromptProjection,
|
||||
@@ -2216,6 +2244,48 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn worker_session_history(
|
||||
&self,
|
||||
request: crate::execution::WorkerSessionHistoryRequest,
|
||||
) -> runtime_api::WorkerSessionHistoryAvailability {
|
||||
match self.factory.retained_session_history_page(
|
||||
&request.worker_ref,
|
||||
request.cursor.as_deref(),
|
||||
request.limit,
|
||||
) {
|
||||
Ok(page) => runtime_api::WorkerSessionHistoryAvailability::Page { page },
|
||||
Err(error) => {
|
||||
let reason = match error {
|
||||
session_store::RetainedHistoryReadError::RetentionMissing => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::RetentionMissing
|
||||
}
|
||||
session_store::RetainedHistoryReadError::ActivePointerMissing => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::ActivePointerMissing
|
||||
}
|
||||
session_store::RetainedHistoryReadError::MigrationRequired => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::MigrationRequired
|
||||
}
|
||||
session_store::RetainedHistoryReadError::CorruptLog => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::CorruptLog
|
||||
}
|
||||
session_store::RetainedHistoryReadError::StorageUnavailable => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::StorageUnavailable
|
||||
}
|
||||
session_store::RetainedHistoryReadError::InvalidCursor => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::InvalidCursor
|
||||
}
|
||||
session_store::RetainedHistoryReadError::ResourceLimit => {
|
||||
runtime_api::WorkerSessionHistoryUnavailableReason::ResourceLimit
|
||||
}
|
||||
};
|
||||
runtime_api::WorkerSessionHistoryAvailability::Unavailable {
|
||||
reason,
|
||||
message: error.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn observe_workspace_prompt_projection(
|
||||
&self,
|
||||
projection: worker::WorkspacePromptProjection,
|
||||
|
||||
Reference in New Issue
Block a user