gemini_adk_rs/tasks/types.rs
1//! Task definitions, commands, and observations shared by Live and replay.
2use crate::{
3 flow::{FlowSnapshot, FlowStack},
4 state::State,
5 tool::ToolDispatcher,
6};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::{collections::HashMap, sync::Arc};
11
12/// A session-local activation identity.
13#[derive(
14 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
15)]
16#[serde(transparent)]
17pub struct TaskId(pub String);
18/// A session-local invocation and approval identity.
19#[derive(
20 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
21)]
22#[serde(transparent)]
23pub struct OperationId(pub String);
24/// The installed definition pinned by an activation.
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
26pub struct SkillKey {
27 /// Installed name.
28 pub name: String,
29 /// Installed version.
30 pub version: String,
31}
32/// Input/output validation supplied by the authoring layer.
33pub type ValueValidator = Arc<dyn Fn(&Value) -> Result<(), String> + Send + Sync>;
34/// Builds fresh governance for each activation.
35pub type FlowFactory = Arc<dyn Fn() -> Result<FlowStack, String> + Send + Sync>;
36/// Binds all captured state in tools to an isolated invocation state.
37pub type TaskToolFactory = Arc<dyn Fn(State) -> Result<ToolDispatcher, String> + Send + Sync>;
38/// Projects declared outputs from accepted task state.
39pub type OutputProjector = Arc<dyn Fn(&State) -> Result<Value, String> + Send + Sync>;
40/// Reconstructs declared state effects from a verified external receipt without executing a tool.
41pub type ReceiptApplier = Arc<dyn Fn(&str, &Value, &State) -> Result<(), String> + Send + Sync>;
42/// Builds fresh service registries, counters and resource bindings for an activation.
43pub type TaskServicesFactory = Arc<dyn Fn() -> Result<super::TaskServices, String> + Send + Sync>;
44/// Executable capability definition. Trusted closures must not capture unrelated mutable state.
45#[derive(Clone)]
46pub struct CompiledSkill {
47 /// Optional per-activation extraction, derivation, watchers, patterns and memory.
48 pub services: Option<TaskServicesFactory>,
49 /// Catalog lookup name.
50 pub name: String,
51 /// Version pinned on activation.
52 pub version: String,
53 /// Routing description.
54 pub description: String,
55 /// Foreground speaking instruction.
56 pub instruction: String,
57 /// JSON input contract.
58 pub input_schema: Value,
59 /// JSON output contract.
60 pub output_schema: Value,
61 /// Compiled input boundary check.
62 pub validate_input: ValueValidator,
63 /// Compiled output boundary check.
64 pub validate_output: ValueValidator,
65 /// Independent defaults copied at activation.
66 pub initial_state: HashMap<String, Value>,
67 /// Optional conversation governance.
68 pub flow_factory: Option<FlowFactory>,
69 /// Invocation-local tools.
70 pub tool_factory: TaskToolFactory,
71 /// Available tool metadata and effect policies.
72 pub tools: Vec<TaskTool>,
73 /// When present, completion exports these accepted state values instead of caller output.
74 pub project_output: Option<OutputProjector>,
75 /// Pure state reconstruction for trusted reconciliation. Custom stateful tools
76 /// must supply their own mapping; failed-worker writes are never trusted.
77 pub receipt_applier: Option<ReceiptApplier>,
78 /// The skill's code tool, declared as `{skill}__run_code`: a program whose
79 /// functions are the skill's own tools, by their local names. Each call
80 /// it makes is an invocation of the task, governed like a direct one.
81 pub code: Option<crate::code_mode::CodeMode>,
82}
83/// One declared operation. Names are local to a skill.
84#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
85pub struct TaskTool {
86 /// Local tool name.
87 pub name: String,
88 /// Model-facing purpose.
89 pub description: String,
90 /// Function argument schema.
91 pub parameters: Option<Value>,
92 /// Whether this operation can commit an external effect.
93 pub effect: TaskToolEffect,
94}
95/// Effects determine admission, approval, cancellation and reconciliation.
96#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
97#[serde(tag = "kind", rename_all = "snake_case")]
98pub enum TaskToolEffect {
99 /// Does not commit an external mutation.
100 Read,
101 /// Requires explicit approval and a complete key passed to the external adapter.
102 Commit {
103 /// String argument carrying the external idempotency key.
104 idempotency_argument: String,
105 },
106}
107/// Trusted application/UI command. Models must never receive Decide or Reconcile.
108#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
109#[serde(tag = "action", rename_all = "snake_case")]
110pub enum TaskCommand {
111 /// Start a new independent activation and make it foreground.
112 Start {
113 /// Installed skill name.
114 skill: String,
115 /// Input validated against that skill's contract.
116 #[serde(default = "empty_object")]
117 input: Value,
118 /// Optional parent resumed when this child finishes.
119 #[serde(default)]
120 parent: Option<TaskId>,
121 },
122 /// Suspend a task without losing its accepted progress.
123 Suspend {
124 #[doc = "Activation to suspend."]
125 task: TaskId,
126 },
127 /// Make an existing nonterminal activation foreground.
128 Resume {
129 #[doc = "Activation to resume."]
130 task: TaskId,
131 },
132 /// Replace inputs and governance, invalidating prior work and approvals.
133 Revise {
134 /// Activation to revise.
135 task: TaskId,
136 /// Revision the caller observed.
137 expected_revision: u64,
138 /// Complete replacement input.
139 input: Value,
140 },
141 /// Cancel this task; an already dispatched commit may still finish.
142 Cancel {
143 #[doc = "Activation to cancel."]
144 task: TaskId,
145 },
146 /// Validate and export a task's result; resume its suspended parent.
147 Complete {
148 /// Activation to finish.
149 task: TaskId,
150 /// Output used when the skill has no state projector; defaults to an empty object.
151 #[serde(default = "empty_object")]
152 output: Value,
153 },
154 /// Admit a tool for the foreground activation.
155 Invoke {
156 /// Activation whose tool should run.
157 task: TaskId,
158 /// Skill-local tool name.
159 tool: String,
160 /// Exact proposed arguments.
161 args: Value,
162 /// Stable external operation key for a commit.
163 #[serde(default)]
164 idempotency_key: Option<String>,
165 },
166 /// Trusted decision about an immutable pending proposal.
167 Decide {
168 /// Pending operation to approve or decline.
169 operation: OperationId,
170 /// Whether the exact proposal is approved.
171 approve: bool,
172 },
173 /// Trusted externally verified resolution of an unknown commit outcome.
174 Reconcile {
175 /// Operation whose external outcome was verified.
176 operation: OperationId,
177 /// Verified result.
178 outcome: ReconciledOutcome,
179 },
180}
181/// An externally verified commit result, not an inference from cancellation.
182#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
183#[serde(tag = "status", rename_all = "snake_case")]
184pub enum ReconciledOutcome {
185 /// External service confirms the operation succeeded.
186 Succeeded {
187 #[doc = "Verified external result."]
188 result: Value,
189 },
190 /// External service confirms no committed effect remains.
191 Failed {
192 #[doc = "Verified failure description."]
193 error: String,
194 },
195}
196/// Task lifecycle, separate from the lifecycle of its external operations.
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
198#[serde(rename_all = "snake_case")]
199pub enum TaskStatus {
200 /// Owns the speaking foreground.
201 Running,
202 /// Retains progress while another task speaks.
203 Suspended,
204 /// Exported a validated output.
205 Completed,
206 /// Cancelled; external operations retain independent receipts.
207 Cancelled,
208 /// Could not continue.
209 Failed,
210}
211/// Operation lifecycle, including uncertainty after an interrupted commit.
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
213#[serde(rename_all = "snake_case")]
214pub enum OperationStatus {
215 /// Waiting for a trusted decision about exact arguments.
216 AwaitingApproval,
217 /// Released to an owned worker.
218 Running,
219 /// Successful result was observed.
220 Succeeded,
221 /// Read failed, or an external non-commit was verified.
222 Failed,
223 /// User declined or a proposal was invalidated.
224 Declined,
225 /// Execution was revoked or a read was cancelled.
226 Cancelled,
227 /// A dispatched commit has no reliable external outcome yet.
228 Unknown,
229}
230/// Identity carried through contextual tool execution.
231#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
232pub struct TaskOwner {
233 /// Owning activation.
234 pub task: TaskId,
235 /// Owning input revision.
236 pub revision: u64,
237 /// Owning invocation.
238 pub operation: OperationId,
239}
240/// Discoverable installed skill metadata.
241#[derive(Debug, Clone, Serialize)]
242pub struct SkillSummary {
243 /// Installed name/version.
244 pub key: SkillKey,
245 /// Routing description.
246 pub description: String,
247 /// Input contract.
248 pub input_schema: Value,
249 /// Output contract.
250 pub output_schema: Value,
251 /// Skill-local tool declarations.
252 pub tools: Vec<TaskTool>,
253}
254/// One activation's observable progress. Raw state is deliberately excluded.
255#[derive(Debug, Clone, Serialize)]
256pub struct TaskSnapshot {
257 /// Whether this task has running or queued service work.
258 #[serde(default)]
259 pub services_pending: bool,
260 /// Recent owned service failures, without raw provider payloads.
261 #[serde(default)]
262 pub service_errors: Vec<String>,
263 /// Activation ID.
264 pub id: TaskId,
265 /// Optional parent resumed after child completion.
266 pub parent: Option<TaskId>,
267 /// Pinned definition.
268 pub skill: SkillKey,
269 /// Current input revision.
270 pub revision: u64,
271 /// Activation lifecycle.
272 pub status: TaskStatus,
273 /// Current task-local flow observation.
274 pub flow: Option<FlowSnapshot>,
275 /// Current pending operation, including approval requests.
276 pub pending: Option<OperationId>,
277 /// Validated completion output.
278 pub output: Option<Value>,
279}
280/// An operation receipt or immutable approval proposal.
281#[derive(Debug, Clone, Serialize)]
282pub struct OperationSnapshot {
283 /// Stable invocation/approval identity.
284 pub id: OperationId,
285 /// Original owning activation and revision.
286 pub owner: TaskOwner,
287 /// Local tool name.
288 pub tool: String,
289 /// Exact admitted arguments; approval never substitutes them.
290 pub args: Value,
291 /// External operation key for commit tools.
292 pub idempotency_key: Option<String>,
293 /// Current operation status.
294 pub status: OperationStatus,
295 /// Cancellation requested after dispatch; not a claim that an effect was undone.
296 pub cancellation_requested: bool,
297 /// Successful result receipt, if known.
298 pub result: Option<Value>,
299 /// Failure or uncertainty description.
300 pub error: Option<String>,
301}
302/// Complete observable task session. This is not a durable restore checkpoint.
303#[derive(Debug, Clone, Serialize)]
304pub struct TaskSessionSnapshot {
305 /// Currently speaking activation.
306 pub foreground: Option<TaskId>,
307 /// Installed catalog.
308 pub skills: Vec<SkillSummary>,
309 /// All activation observations.
310 pub tasks: Vec<TaskSnapshot>,
311 /// Pending proposals and retained operation outcomes.
312 pub operations: Vec<OperationSnapshot>,
313}
314/// A tool completion's publication decision.
315#[derive(Debug)]
316pub struct TaskDelivery {
317 /// Operation the host correlates to its protocol call.
318 pub operation_id: OperationId,
319 /// Original task owner.
320 pub task_id: TaskId,
321 /// Original input revision.
322 pub revision: u64,
323 /// Tool result for the host to format.
324 pub result: Result<Value, String>,
325 /// Valid completion for the foreground task.
326 pub foreground: bool,
327 /// Completion did not update the current activation revision.
328 pub stale: bool,
329}
330/// Configuration, admission or lifecycle error.
331#[derive(Debug, thiserror::Error)]
332#[error("task runtime: {0}")]
333pub struct TaskError(pub String);
334
335fn empty_object() -> Value {
336 Value::Object(serde_json::Map::new())
337}
338
339/// The longest skill name: its entry point, `start_{skill}`, must fit the
340/// Live API's 64-character function names.
341pub const MAX_SKILL_NAME: usize = 64 - "start_".len();
342
343/// Validate identity shared by compiled catalogs and authoring documents.
344pub fn validate_skill_identity(name: &str, version: &str) -> Result<(), TaskError> {
345 if !valid_name(name) || version.trim().is_empty() {
346 return Err(TaskError(
347 "skill names must be ASCII identifiers without '__', and versions must be nonempty"
348 .into(),
349 ));
350 }
351 if name.len() > MAX_SKILL_NAME {
352 return Err(TaskError(format!(
353 "skill name '{name}' is {} characters; at most {MAX_SKILL_NAME} fit, so that \
354 start_{name} fits the 64-character function name limit",
355 name.len()
356 )));
357 }
358 Ok(())
359}
360impl TaskTool {
361 /// Validate the local name, qualified provider name and commit key policy.
362 pub fn validate_for_skill(&self, skill: &str) -> Result<(), TaskError> {
363 if !valid_name(&self.name) || skill.len() + 2 + self.name.len() > 64 {
364 return Err(TaskError(format!(
365 "tool '{}' must be an ASCII identifier without '__'; its qualified name must fit 64 characters",
366 self.name
367 )));
368 }
369 if let TaskToolEffect::Commit {
370 idempotency_argument,
371 } = &self.effect
372 && idempotency_argument.trim().is_empty()
373 {
374 return Err(TaskError(
375 "commit idempotency argument must be nonempty".into(),
376 ));
377 }
378 Ok(())
379 }
380}
381fn valid_name(name: &str) -> bool {
382 !name.contains("__")
383 && name.as_bytes().first().is_some_and(u8::is_ascii_alphabetic)
384 && name.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'_')
385}