core: type streaming session timestamps

This commit is contained in:
a.tolmachev
2026-04-13 05:47:42 +00:00
parent edf318ba67
commit 03cb109b40
10 changed files with 254 additions and 228 deletions
+45 -47
View File
@@ -153,7 +153,8 @@ pub struct WindowTestRunView {
pub struct StreamSessionStartView {
pub session_id: String,
pub status: StreamStatus,
pub expires_at: String,
#[serde(with = "time::serde::rfc3339")]
pub expires_at: OffsetDateTime,
pub poll_after_ms: u64,
pub preview: Value,
}
@@ -225,7 +226,7 @@ impl TestRunOutcome {
json!({
"session_id": output.session_id,
"status": output.status,
"expires_at": output.expires_at,
"expires_at": format_timestamp(output.expires_at),
"poll_after_ms": output.poll_after_ms,
"preview": output.preview,
}),
@@ -451,10 +452,14 @@ pub struct StreamSessionSummaryView {
pub protocol: Protocol,
pub mode: ExecutionMode,
pub status: StreamStatus,
pub expires_at: String,
pub last_poll_at: Option<String>,
pub created_at: String,
pub closed_at: Option<String>,
#[serde(with = "time::serde::rfc3339")]
pub expires_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub last_poll_at: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub closed_at: Option<OffsetDateTime>,
}
#[derive(Clone, Debug, Serialize)]
@@ -480,10 +485,14 @@ pub struct AsyncJobSummaryView {
pub agent_id: Option<String>,
pub status: JobStatus,
pub progress: Value,
pub created_at: String,
pub updated_at: String,
pub finished_at: Option<String>,
pub expires_at: Option<String>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub finished_at: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")]
pub expires_at: Option<OffsetDateTime>,
}
#[derive(Clone, Debug, Serialize)]
@@ -1551,7 +1560,7 @@ impl AdminService {
})?;
ensure_stream_session_workspace(&session, workspace_id)?;
self.registry
.close_stream_session(session_id, &now_string()?)
.close_stream_session(session_id, &OffsetDateTime::now_utc())
.await?;
let updated = self
.registry
@@ -1621,7 +1630,7 @@ impl AdminService {
})?;
ensure_async_job_workspace(&job, workspace_id)?;
self.registry
.cancel_async_job(job_id, &now_string()?)
.cancel_async_job(job_id, &OffsetDateTime::now_utc())
.await?;
let updated = self.registry.get_async_job(job_id).await?.ok_or_else(|| {
ApiError::not_found(format!("async job {} was not found", job_id.as_str()))
@@ -2186,16 +2195,14 @@ impl AdminService {
let preview_count = seed.items.len().min(batch_size);
let preview_items = seed.items[..preview_count].to_vec();
let next_index = preview_count;
let created_at = now_string().map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
let expires_at = add_millis(
&created_at,
streaming.max_session_lifetime_ms.unwrap_or(60_000),
)
.map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
let created_at = OffsetDateTime::now_utc();
let expires_at = created_at
.checked_add(time::Duration::milliseconds(
streaming.max_session_lifetime_ms.unwrap_or(60_000) as i64,
))
.ok_or_else(|| RuntimeError::SecretCrypto {
details: "failed to compute stream session expiration".to_owned(),
})?;
let session = StreamSession {
id: crank_core::StreamSessionId::new(new_prefixed_id("sess")),
workspace_id: workspace_id.clone(),
@@ -2212,8 +2219,8 @@ impl AdminService {
next_index,
batch_size,
}),
expires_at: expires_at.clone(),
last_poll_at: Some(created_at.clone()),
expires_at,
last_poll_at: Some(created_at),
created_at,
closed_at: None,
};
@@ -2243,21 +2250,19 @@ impl AdminService {
runtime: &RuntimeOperation,
input: &Value,
) -> Result<AsyncJobStartView, RuntimeError> {
let created_at = now_string().map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
let created_at = OffsetDateTime::now_utc();
let streaming = runtime.execution_config.streaming.as_ref().ok_or_else(|| {
RuntimeError::MissingStreamingConfig {
operation_id: runtime.operation_id.as_str().to_owned(),
}
})?;
let expires_at = add_millis(
&created_at,
streaming.max_session_lifetime_ms.unwrap_or(300_000),
)
.map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
let expires_at = created_at
.checked_add(time::Duration::milliseconds(
streaming.max_session_lifetime_ms.unwrap_or(300_000) as i64,
))
.ok_or_else(|| RuntimeError::SecretCrypto {
details: "failed to compute async job expiration".to_owned(),
})?;
let job = AsyncJobHandle {
id: crank_core::AsyncJobId::new(new_prefixed_id("job")),
workspace_id: workspace_id.clone(),
@@ -2268,8 +2273,8 @@ impl AdminService {
result: None,
error: None,
expires_at: Some(expires_at),
created_at: created_at.clone(),
updated_at: created_at.clone(),
created_at,
updated_at: created_at,
finished_at: None,
};
self.registry
@@ -2306,10 +2311,7 @@ impl AdminService {
}
Err(error) => Err(error),
};
let finished_at = match now_string() {
Ok(value) => value,
Err(_) => return,
};
let finished_at = OffsetDateTime::now_utc();
let _ = match result {
Ok(output) => {
registry
@@ -4613,14 +4615,10 @@ fn now_string() -> Result<String, ApiError> {
.map_err(|error| ApiError::internal(error.to_string()))
}
fn add_millis(timestamp: &str, millis: u64) -> Result<String, ApiError> {
let parsed = OffsetDateTime::parse(timestamp, &Rfc3339)
.map_err(|error| ApiError::internal(error.to_string()))?;
parsed
.checked_add(time::Duration::milliseconds(millis as i64))
.ok_or_else(|| ApiError::internal("failed to add millis to timestamp"))?
fn format_timestamp(timestamp: OffsetDateTime) -> String {
timestamp
.format(&Rfc3339)
.map_err(|error| ApiError::internal(error.to_string()))
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned())
}
async fn resolve_runtime_auth_for_task(