scv_protocol/background.rs
1//! Background delegation jobs, as clients see them.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// Why the server started a turn on its own.
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9pub struct TurnOrigin {
10 /// What the turn is for.
11 pub kind: OriginKind,
12 /// The background jobs this turn reports. Once it starts, the model has
13 /// seen their results.
14 #[serde(default, skip_serializing_if = "Vec::is_empty")]
15 pub jobs: Vec<String>,
16}
17
18/// What a server-started turn is for.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21#[non_exhaustive]
22pub enum OriginKind {
23 /// Finished background delegations are being reported.
24 Background,
25 /// A kind this client does not know, from a newer server.
26 #[serde(other)]
27 Unknown,
28}
29
30/// Where a background job stands, with the strings the model reads in the
31/// job tools' results.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34#[non_exhaustive]
35pub enum JobStatus {
36 /// Still working.
37 Running,
38 /// The agent finished its task.
39 Completed,
40 /// The agent or its run failed.
41 Failed,
42 /// The agent's model refused the request.
43 Declined,
44 /// The run reached its time limit.
45 Timeout,
46 /// Stopped on request.
47 Cancelled,
48 /// A status this client does not know, from a newer server.
49 #[serde(other)]
50 Unknown,
51}
52
53impl JobStatus {
54 /// The status as the model reads it, such as `completed`.
55 pub fn as_str(self) -> &'static str {
56 match self {
57 Self::Running => "running",
58 Self::Completed => "completed",
59 Self::Failed => "failed",
60 Self::Declined => "declined",
61 Self::Timeout => "timeout",
62 Self::Cancelled => "cancelled",
63 Self::Unknown => "unknown",
64 }
65 }
66}
67
68impl fmt::Display for JobStatus {
69 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
70 formatter.write_str(self.as_str())
71 }
72}
73
74/// A background job a tool call started, or whose result the call showed
75/// the model (`tool.completed.jobs`). A session's clients keep it open while
76/// its jobs run, since closing the session cancels them.
77///
78/// A job appears once as `running`, from the `agent_*` call that started it,
79/// and once more with how it ended, from the `agent_wait`, `agent_status`, or
80/// `agent_cancel` call through which the model saw that result. A job whose
81/// result the model sees in a report turn is settled by that turn's
82/// [`TurnOrigin::jobs`] instead.
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
84pub struct JobChange {
85 /// The job's handle, such as `job-1`.
86 pub job: String,
87 /// The delegating tool, such as `agent_codex`.
88 pub tool: String,
89 /// `running` when the call started it; otherwise how it ended.
90 pub status: JobStatus,
91 /// The first line of the delegated prompt, shortened; empty when unknown.
92 #[serde(default, skip_serializing_if = "String::is_empty")]
93 pub task: String,
94}
95
96impl JobChange {
97 /// Whether the call started the job, rather than settled it.
98 pub fn started(&self) -> bool {
99 self.status == JobStatus::Running
100 }
101}