core: type streaming session timestamps
This commit is contained in:
@@ -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(
|
||||
|
||||
+30
-28
@@ -741,8 +741,8 @@ async fn handle_session_start_call(
|
||||
let next_index = preview_count;
|
||||
let session_id =
|
||||
StreamSessionId::new(format!("sess_{}", uuid::Uuid::now_v7().simple()));
|
||||
let now = now_rfc3339();
|
||||
let expires_at = add_millis(&now, streaming.max_session_lifetime_ms.unwrap_or(60_000));
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let expires_at = add_millis(now, streaming.max_session_lifetime_ms.unwrap_or(60_000));
|
||||
let session_record = StreamSession {
|
||||
id: session_id.clone(),
|
||||
workspace_id: tool.workspace_id.clone(),
|
||||
@@ -759,9 +759,9 @@ async fn handle_session_start_call(
|
||||
next_index,
|
||||
batch_size,
|
||||
}),
|
||||
expires_at: expires_at.clone(),
|
||||
last_poll_at: Some(now.clone()),
|
||||
created_at: now.clone(),
|
||||
expires_at,
|
||||
last_poll_at: Some(now),
|
||||
created_at: now,
|
||||
closed_at: None,
|
||||
};
|
||||
|
||||
@@ -778,7 +778,7 @@ async fn handle_session_start_call(
|
||||
let output = json!({
|
||||
"session_id": session_id.as_str(),
|
||||
"status": "running",
|
||||
"expires_at": expires_at,
|
||||
"expires_at": format_rfc3339(expires_at),
|
||||
"poll_after_ms": streaming.poll_interval_ms.unwrap_or(1000),
|
||||
"preview": {
|
||||
"summary": seed.summary,
|
||||
@@ -873,7 +873,7 @@ async fn handle_session_poll_call(
|
||||
);
|
||||
};
|
||||
|
||||
let now = now_rfc3339();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let loaded = match state
|
||||
.registry
|
||||
.get_stream_session(&StreamSessionId::new(control.session_id.clone()))
|
||||
@@ -902,7 +902,7 @@ async fn handle_session_poll_call(
|
||||
);
|
||||
}
|
||||
|
||||
if loaded.is_expired(&now) {
|
||||
if loaded.is_expired(now) {
|
||||
let _ = state
|
||||
.registry
|
||||
.update_stream_session_state(UpdateStreamSessionStateRequest {
|
||||
@@ -926,7 +926,7 @@ async fn handle_session_poll_call(
|
||||
);
|
||||
}
|
||||
|
||||
if !loaded.can_poll(&now) {
|
||||
if !loaded.can_poll(now) {
|
||||
return tool_error_response(
|
||||
message,
|
||||
response_mode,
|
||||
@@ -970,11 +970,11 @@ async fn handle_session_poll_call(
|
||||
cursor: cursor.as_ref(),
|
||||
state: &json!(state_payload),
|
||||
expires_at: Some(&add_millis(
|
||||
&now,
|
||||
now,
|
||||
streaming.max_session_lifetime_ms.unwrap_or(60_000),
|
||||
)),
|
||||
last_poll_at: Some(&now),
|
||||
closed_at: matches!(next_status, StreamStatus::Stopped).then_some(now.as_str()),
|
||||
closed_at: matches!(next_status, StreamStatus::Stopped).then_some(&now),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -985,7 +985,7 @@ async fn handle_session_poll_call(
|
||||
let output = json!({
|
||||
"session_id": updated.id.as_str(),
|
||||
"status": serialize_stream_status(updated.status),
|
||||
"expires_at": updated.expires_at,
|
||||
"expires_at": format_rfc3339(updated.expires_at),
|
||||
"summary": serde_json::from_value::<StoredSessionState>(updated.state.clone()).map(|value| value.summary).unwrap_or(Value::Null),
|
||||
"items": items,
|
||||
"cursor": updated.cursor,
|
||||
@@ -1069,7 +1069,7 @@ async fn handle_session_stop_call(
|
||||
.registry
|
||||
.close_stream_session(
|
||||
&StreamSessionId::new(control.session_id.clone()),
|
||||
&now_rfc3339(),
|
||||
&OffsetDateTime::now_utc(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -1106,7 +1106,7 @@ async fn handle_async_job_start_call(
|
||||
);
|
||||
};
|
||||
|
||||
let now = now_rfc3339();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let job = AsyncJobHandle {
|
||||
id: AsyncJobId::new(format!("job_{}", uuid::Uuid::now_v7().simple())),
|
||||
workspace_id: tool.workspace_id.clone(),
|
||||
@@ -1117,11 +1117,11 @@ async fn handle_async_job_start_call(
|
||||
result: None,
|
||||
error: None,
|
||||
expires_at: Some(add_millis(
|
||||
&now,
|
||||
now,
|
||||
streaming.max_session_lifetime_ms.unwrap_or(300_000),
|
||||
)),
|
||||
created_at: now.clone(),
|
||||
updated_at: now.clone(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
finished_at: None,
|
||||
};
|
||||
|
||||
@@ -1156,7 +1156,7 @@ async fn handle_async_job_start_call(
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
};
|
||||
let finished_at = now_rfc3339();
|
||||
let finished_at = OffsetDateTime::now_utc();
|
||||
|
||||
let update_result = match result {
|
||||
Ok(output) => {
|
||||
@@ -1424,7 +1424,10 @@ async fn handle_async_job_cancel_call(
|
||||
|
||||
match state
|
||||
.registry
|
||||
.cancel_async_job(&AsyncJobId::new(control.job_id.clone()), &now_rfc3339())
|
||||
.cancel_async_job(
|
||||
&AsyncJobId::new(control.job_id.clone()),
|
||||
&OffsetDateTime::now_utc(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => success_tool_response(
|
||||
@@ -1909,21 +1912,20 @@ fn tool_error_response(
|
||||
)
|
||||
}
|
||||
|
||||
fn now_rfc3339() -> String {
|
||||
OffsetDateTime::now_utc()
|
||||
fn format_rfc3339(timestamp: OffsetDateTime) -> String {
|
||||
timestamp
|
||||
.format(&Rfc3339)
|
||||
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned())
|
||||
}
|
||||
|
||||
fn add_millis(timestamp: &str, millis: u64) -> String {
|
||||
let Ok(parsed) = OffsetDateTime::parse(timestamp, &Rfc3339) else {
|
||||
return timestamp.to_owned();
|
||||
};
|
||||
fn now_rfc3339() -> String {
|
||||
format_rfc3339(OffsetDateTime::now_utc())
|
||||
}
|
||||
|
||||
fn add_millis(timestamp: OffsetDateTime, millis: u64) -> OffsetDateTime {
|
||||
let delta = time::Duration::milliseconds(i64::try_from(millis).unwrap_or(i64::MAX));
|
||||
|
||||
(parsed + delta)
|
||||
.format(&Rfc3339)
|
||||
.unwrap_or_else(|_| timestamp.to_owned())
|
||||
timestamp + delta
|
||||
}
|
||||
|
||||
fn serialize_stream_status(status: StreamStatus) -> &'static str {
|
||||
|
||||
Reference in New Issue
Block a user