Skip to main content

layover_http/
generated.rs

1//! The Layover HTTP surface, generated from `api/openapi.yaml`.
2//!
3//! @generated by `cargo xtask generate-api`. Do not edit by hand: `cargo xtask verify`
4//! regenerates this file and fails if the result differs, so an edit here is reverted
5//! rather than kept. Change the specification instead.
6//!
7//! Source: Layover Tower API v0.23.1
8
9#![allow(clippy::too_many_lines)]
10
11use axum::response::IntoResponse as _;
12use serde::{Deserialize, Serialize};
13
14use crate::EventStream;
15
16/// Whether an agent writes to the shared workspace or gets a snapshot.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum Access {
19    /// `read-only`
20    #[serde(rename = "read-only")]
21    ReadOnly,
22    /// `read-write`
23    #[serde(rename = "read-write")]
24    ReadWrite,
25}
26
27/// One configured agent.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct Agent {
30    /// Whether the agent writes to the shared workspace.
31    pub access: Access,
32    /// One line saying what the agent is.
33    #[serde(default)]
34    pub description: Option<String>,
35    /// Whether a human may send flights straight to this agent.
36    pub entry: bool,
37    /// Model identifier passed to the runner.
38    #[serde(default)]
39    pub model: Option<String>,
40    /// The key the agent is declared under, and what routes refer to.
41    pub name: String,
42    /// A longer statement of when to route work here.
43    #[serde(default)]
44    pub purpose: Option<String>,
45    /// Whether the agent is pinned resident rather than transient.
46    pub resident: bool,
47    /// Which runner invokes this agent's CLI.
48    #[serde(default)]
49    pub runner: Option<String>,
50}
51
52/// The factory's agents and the edges between them.
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub struct AgentList {
55    /// Every configured agent.
56    pub agents: Vec<Agent>,
57    /// The route map.
58    pub routes: Vec<Route>,
59}
60
61/// What kind of thing an agent is stuck on. Coarse on purpose: the point is to make "half my
62/// runs are stuck on credentials" visible at a glance, with the detail in the prose.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum Blocker {
65    /// `access`
66    #[serde(rename = "access")]
67    Access,
68    /// `tooling`
69    #[serde(rename = "tooling")]
70    Tooling,
71    /// `ambiguity`
72    #[serde(rename = "ambiguity")]
73    Ambiguity,
74    /// `environment`
75    #[serde(rename = "environment")]
76    Environment,
77    /// `decision`
78    #[serde(rename = "decision")]
79    Decision,
80    /// `other`
81    #[serde(rename = "other")]
82    Other,
83}
84
85/// One named slice of spend, such as an agent or a model.
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub struct CostBucket {
88    /// The agent or model this slice belongs to.
89    pub name: String,
90    /// Totals for this slice.
91    pub summary: CostSummary,
92}
93
94/// Where the factory's money went.
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub struct CostReport {
97    /// Spend per agent, most expensive first.
98    pub by_agent: Vec<CostBucket>,
99    /// Spend per model, most expensive first. Runs with no reported model are omitted.
100    pub by_model: Vec<CostBucket>,
101    /// Spend per workflow, most expensive first. The breakdown a factory with several
102    /// pipelines actually needs: per-agent totals cannot answer "what does the nightly sweep
103    /// cost" once an agent belongs to more than one workflow, and most of them do.
104    pub by_pipeline: Vec<CostBucket>,
105    /// The factory-wide ceiling and what is left of it.
106    pub reserve: ReserveState,
107    /// The period these totals cover, including the zone it was reckoned in and whether it
108    /// outruns retention. Part of the number, not decoration.
109    pub span: WindowSpan,
110    /// Totals across every run in scope.
111    pub total: CostSummary,
112}
113
114/// Where a cost figure came from, worst-first. `reported` is the only kind worth billing
115/// against; `rate_card` was derived from token counts and published prices; `unreported`
116/// means the runner said nothing, so the figure is zero and means nothing.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
118pub enum CostSource {
119    /// `reported`
120    #[serde(rename = "reported")]
121    Reported,
122    /// `rate_card`
123    #[serde(rename = "rate_card")]
124    RateCard,
125    /// `unreported`
126    #[serde(rename = "unreported")]
127    Unreported,
128}
129
130/// Totals over some set of runs, carrying their own confidence.
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct CostSummary {
133    /// The weakest source contributing to this total.
134    pub confidence: CostSource,
135    /// Runs priced from a rate card rather than measured.
136    pub estimated_runs: i32,
137    /// Fraction of runs whose cost the runner actually reported, 0.0 to 1.0.
138    pub measured_share: f64,
139    /// How many runs contributed.
140    pub runs: i32,
141    /// Runs whose runner reported no cost at all.
142    pub unreported_runs: i32,
143    /// Total tokens.
144    pub usage: TokenUsage,
145    /// Total cost in US dollars.
146    pub usd: f64,
147}
148
149/// A period to report over.
150///
151/// Two kinds, and the difference matters. `today` and `month_to_date` are **calendar** windows: they
152/// begin at local midnight, so which instant that is depends on the Tower's time zone. The
153/// rest are **rolling**: a fixed number of hours ending now, identical everywhere.
154///
155/// Conflating the two is not theoretical. Gating spend on a UTC day boundary while reporting
156/// the ledger in local time lets a factory spend one day's money twice, and the bug is
157/// invisible until it matters.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159pub enum CostWindow {
160    /// `today`
161    #[serde(rename = "today")]
162    Today,
163    /// `last_24h`
164    #[serde(rename = "last_24h")]
165    Last24h,
166    /// `last_7d`
167    #[serde(rename = "last_7d")]
168    Last7d,
169    /// `last_30d`
170    #[serde(rename = "last_30d")]
171    Last30d,
172    /// `month_to_date`
173    #[serde(rename = "month_to_date")]
174    MonthToDate,
175    /// `last_90d`
176    #[serde(rename = "last_90d")]
177    Last90d,
178    /// `all_time`
179    #[serde(rename = "all_time")]
180    AllTime,
181}
182
183/// A boolean parameter a pipeline accepts at trigger time.
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub struct Flag {
186    /// Value used when the trigger does not set the flag.
187    pub default: bool,
188    /// What turning this flag on actually does.
189    #[serde(default)]
190    pub description: Option<String>,
191    /// The name a prompt tests with `@include(name)`.
192    pub name: String,
193}
194
195/// A flag resolved to the value one run will see. Distinct from `Flag`, which declares what a pipeline accepts: a declaration carries a default, and reporting that default back as though it were the operator's choice would hide the difference between a flag left alone and a flag deliberately set to the same value.
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub struct FlagValue {
198    /// The name a prompt tests with `@include(name)`.
199    pub name: String,
200    /// The value this run will see.
201    pub value: bool,
202}
203
204/// Confirmation that work has been started.
205#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
206pub struct FlightAccepted {
207    /// Identifier of the flight that was created.
208    pub flight_id: String,
209    /// Identifier of the new chain, which carries Hops and Fuel.
210    pub itinerary_id: String,
211    /// The agent the flight was addressed to.
212    pub to: String,
213}
214
215/// Whether everything is halted.
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217pub struct GroundStop {
218    /// True when no new work may start.
219    pub engaged: bool,
220    /// When the Ground Stop was engaged.
221    #[serde(default)]
222    pub since: Option<String>,
223}
224
225/// Whether the Tower is up and whether it is allowed to do anything.
226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
227pub struct Health {
228    /// True when a Ground Stop is engaged and no new work may start.
229    pub ground_stop: bool,
230    /// Always `ok`; failure is signalled by not answering at all.
231    pub status: Status,
232    /// The Tower's own version.
233    pub version: String,
234}
235
236/// Matching help requests, most recent first.
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
238pub struct HelpList {
239    /// How many of them nobody has dealt with.
240    pub open: i32,
241    /// The requests.
242    pub requests: Vec<HelpRequest>,
243}
244
245/// One agent asking a human for something.
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct HelpRequest {
248    /// Who is asking.
249    pub agent: String,
250    /// When it was raised.
251    pub at: String,
252    /// What kind of thing is in the way.
253    pub blocker: Blocker,
254    /// What was tried, what happened, and what is needed.
255    pub detail: String,
256    /// Whether this stopped the work or merely limited it. An agent can finish its task and
257    /// still have been unable to check something; that is worth reporting and is not an
258    /// outage.
259    pub fatal: bool,
260    /// The chain it belonged to.
261    pub itinerary_id: String,
262    /// The workflow whose run raised it, derived from the itinerary rather than stored on the
263    /// request. Null when no run for that itinerary is still in history, which retention can
264    /// cause at the far edge of the window.
265    #[serde(default)]
266    pub pipeline: Option<String>,
267    /// When a human marked it dealt with. Null while it is still open.
268    #[serde(default)]
269    pub resolved_at: Option<String>,
270    /// The run that raised it.
271    pub run_id: String,
272    /// One line, for a list.
273    pub summary: String,
274}
275
276/// The result of resolving.
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub struct HelpResolved {
279    /// How many open requests were marked. Zero is not an error: somebody else may have
280    /// resolved them, or the narrowing may have matched nothing.
281    pub resolved: i32,
282}
283
284/// How much applying a learning would change a future run. Self-assessed, and therefore used
285/// for display and triage only — never to decide whether a learning applies.
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
287pub enum Impact {
288    /// `low`
289    #[serde(rename = "low")]
290    Low,
291    /// `medium`
292    #[serde(rename = "medium")]
293    Medium,
294    /// `high`
295    #[serde(rename = "high")]
296    High,
297}
298
299/// One causal chain of flights, and the budget it shares.
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
301pub struct Itinerary {
302    /// Agents that ran in this chain, in the order they first ran.
303    #[serde(default)]
304    pub agents: Option<Vec<String>>,
305    /// Why a chain is stalled or halted, when that can be said.
306    #[serde(default)]
307    pub detail: Option<String>,
308    /// When the last run of the chain ended. Null while it is still working.
309    #[serde(default)]
310    pub finished_at: Option<String>,
311    /// Identifier of the chain.
312    pub itinerary_id: String,
313    /// False when any run in the chain reported no cost. A total built partly from silence is
314    /// a floor, not a figure, and showing it as though it were measured invites planning
315    /// against it.
316    #[serde(default)]
317    pub measured: Option<bool>,
318    /// The pipeline it was triggered through, when one was named.
319    #[serde(default)]
320    pub pipeline: Option<String>,
321    /// How many runs the chain has started.
322    pub runs: i32,
323    /// When the first run of the chain began.
324    pub started_at: String,
325    /// What became of the chain.
326    pub state: ItineraryState,
327    /// What the chain has spent, as far as its runners reported.
328    pub usd: f64,
329}
330
331/// Chains of work.
332#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
333pub struct ItineraryList {
334    /// Chains, most recently started first.
335    pub itineraries: Vec<Itinerary>,
336    /// How many of them are stalled. Counted separately so the number can be shown without
337    /// reading the list, because it is the one that should prompt somebody to look.
338    pub stalled: i32,
339}
340
341/// What became of a chain.
342///
343/// `stalled` is the one that matters and the one a list of runs cannot show: every run
344/// succeeded, nothing is live, nothing is queued, and nothing will ever happen again. It is
345/// the failure mode this whole surface exists to make visible.
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
347pub enum ItineraryState {
348    /// `working`
349    #[serde(rename = "working")]
350    Working,
351    /// `finished`
352    #[serde(rename = "finished")]
353    Finished,
354    /// `stalled`
355    #[serde(rename = "stalled")]
356    Stalled,
357    /// `halted`
358    #[serde(rename = "halted")]
359    Halted,
360}
361
362/// The condition under which a rendezvous barrier releases.
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
364pub enum Join {
365    /// `all`
366    #[serde(rename = "all")]
367    All,
368    /// `any`
369    #[serde(rename = "any")]
370    Any,
371}
372
373/// A person's verdict on a learning.
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375pub struct JudgeLearningRequest {
376    /// The verdict.
377    pub state: Judgement,
378}
379
380/// What a person decided about a learning that is already in use.
381///
382/// There is no `provisional` here. A learning starts provisional on its own and becomes
383/// confirmed through independent rediscovery; putting one *back* would discard evidence
384/// already gathered.
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
386pub enum Judgement {
387    /// `confirmed`
388    #[serde(rename = "confirmed")]
389    Confirmed,
390    /// `rejected`
391    #[serde(rename = "rejected")]
392    Rejected,
393}
394
395/// Something an agent worked out, with its history.
396#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
397pub struct Learning {
398    /// The agent this applies to. Learnings do not cross agents.
399    pub agent: String,
400    /// When it was first proposed.
401    pub first_at: String,
402    /// Identifier.
403    pub id: String,
404    /// The agent's own rating, for triage.
405    pub impact: Impact,
406    /// When it was most recently proposed.
407    pub last_at: String,
408    /// How many times it has been independently rediscovered, including the first proposal.
409    /// Repeating advice the agent was just shown does not count — an echo is not evidence.
410    pub proposals: i32,
411    /// Runs before it lapses. Meaningless unless `provisional`.
412    pub runs_left: i32,
413    /// Where it is in its life.
414    pub state: LearningState,
415    /// The insight.
416    pub text: String,
417}
418
419/// Matching learnings.
420#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
421pub struct LearningList {
422    /// How many of them are currently given to runs.
423    pub active: i32,
424    /// The learnings.
425    pub learnings: Vec<Learning>,
426}
427
428/// Where a learning is in its life. `provisional` applies now and will lapse unless
429/// rediscovered; `confirmed` has been rediscovered enough times to be treated as real;
430/// `lapsed` is remembered only so a later rediscovery can be recognised as one; `rejected`
431/// was refused by a human and never counts again.
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
433pub enum LearningState {
434    /// `provisional`
435    #[serde(rename = "provisional")]
436    Provisional,
437    /// `confirmed`
438    #[serde(rename = "confirmed")]
439    Confirmed,
440    /// `lapsed`
441    #[serde(rename = "lapsed")]
442    Lapsed,
443    /// `rejected`
444    #[serde(rename = "rejected")]
445    Rejected,
446}
447
448/// A flight that has been asked for and not yet dispatched.
449#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
450pub struct PendingFlight {
451    /// The prompt the run will be given.
452    pub body: String,
453    /// The flags set for this run.
454    #[serde(default)]
455    pub flags: Option<Vec<FlagValue>>,
456    /// Identifier of the queued flight.
457    pub flight_id: String,
458    /// The chain it will begin.
459    pub itinerary_id: String,
460    /// The pipeline it was triggered through, when one was named.
461    #[serde(default)]
462    pub pipeline: Option<String>,
463    /// When it was asked for.
464    pub queued_at: String,
465    /// The agent it is addressed to.
466    pub to: String,
467}
468
469/// Work waiting to start.
470#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
471pub struct PendingList {
472    /// What will pick this work up, or null when nothing will. Null is the honest answer
473    /// until the supervisor exists, and the dashboard says so rather than showing a queue
474    /// that looks like it is moving.
475    #[serde(default)]
476    pub dispatched_by: Option<String>,
477    /// Queued flights, oldest first.
478    pub pending: Vec<PendingFlight>,
479}
480
481/// A named entry point into the mesh.
482#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
483pub struct Pipeline {
484    /// One line saying what this pipeline is for.
485    #[serde(default)]
486    pub description: Option<String>,
487    /// The agent that receives the first flight.
488    pub entry: String,
489    /// The boolean parameters this pipeline accepts.
490    pub flags: Vec<Flag>,
491    /// Shared budget for a chain started here, honouring the entry agent's own `fuel_usd`
492    /// where it sets one. Bounds **breadth**, which is the dimension Hops cannot see.
493    pub fuel_usd: f64,
494    /// Flights a chain started here may make before it is cut. Bounds **depth**: branches
495    /// inherit the remaining count rather than splitting it, so this says nothing about how
496    /// wide a fan-out spreads.
497    pub max_hops: i32,
498    /// The key the pipeline is declared under.
499    pub name: String,
500    /// True when this pipeline picks up booked layovers rather than starting fresh work. A
501    /// tick that finds nothing due costs nothing.
502    pub resumes: bool,
503    /// What starts it.
504    pub trigger: Trigger,
505    /// Whether instances of this pipeline share a working directory or get one each. A
506    /// schedule fires whether or not the last instance finished, so anything reaching a
507    /// read-write agent wants its own.
508    pub workspace: Workspace,
509}
510
511/// Every declared pipeline.
512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
513pub struct PipelineList {
514    /// The pipelines.
515    pub pipelines: Vec<Pipeline>,
516}
517
518/// An error, shaped after RFC 9457.
519#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
520pub struct Problem {
521    /// An explanation specific to this occurrence.
522    #[serde(default)]
523    pub detail: Option<String>,
524    /// The HTTP status code, repeated in the body for clients that lose it.
525    pub status: i32,
526    /// A short, human-readable summary of the problem.
527    pub title: String,
528}
529
530/// What an agent wrote about its own run.
531#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
532pub struct Report {
533    /// Which agent wrote it.
534    pub agent: String,
535    /// What the run produced or changed — paths, branch names, pull request identifiers.
536    /// Separate from the body so output can be found without reading the prose.
537    #[serde(default)]
538    pub artifacts: Option<Vec<String>>,
539    /// When it was written.
540    pub at: String,
541    /// The report itself, as the agent wrote it.
542    pub body: String,
543    /// One line, for a list. What happened, not what was attempted.
544    pub headline: String,
545    /// The chain it belonged to.
546    pub itinerary_id: String,
547    /// The run this describes.
548    pub run_id: String,
549    /// True when the report was longer than the cap and was cut. Worth knowing: otherwise it
550    /// reads as though the agent simply stopped there.
551    #[serde(default)]
552    pub trimmed: Option<bool>,
553}
554
555/// The factory-wide spend ceiling. Distinct from Fuel, which bounds one itinerary: a
556/// scheduled pipeline mints a fresh itinerary with a fresh Fuel budget on every tick, so
557/// only the Reserve bounds the total. The window rolls rather than resetting at midnight.
558#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
559pub struct ReserveState {
560    /// Ceiling for the window. Null when no ceiling is enforced.
561    #[serde(default)]
562    pub cap_usd: Option<f64>,
563    /// True when the Reserve is currently refusing new work.
564    pub exhausted: bool,
565    /// Left before new work is refused. Null when unlimited.
566    #[serde(default)]
567    pub remaining_usd: Option<f64>,
568    /// Spend within the current window.
569    pub spent_usd: f64,
570    /// How far back the rolling window reaches.
571    pub window_hours: i64,
572}
573
574/// Which open help requests to mark as dealt with.
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
576pub struct ResolveHelpRequest {
577    /// Only requests from this agent.
578    #[serde(default)]
579    pub agent: Option<String>,
580    /// Only requests of this kind.
581    #[serde(default)]
582    pub blocker: Option<Blocker>,
583    /// Only requests raised by this run. The narrowest form, and the one a list with a button
584    /// beside each row uses.
585    #[serde(default)]
586    pub run_id: Option<String>,
587    /// How far back to look. Defaults to the last 30 days.
588    #[serde(default)]
589    pub window: Option<CostWindow>,
590}
591
592/// One entry of the route map, possibly expanding to several edges.
593#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
594pub struct Route {
595    /// Sending agents.
596    pub from: Vec<String>,
597    /// Set when flights on this edge are parked at a rendezvous barrier.
598    #[serde(default)]
599    pub join: Option<Join>,
600    /// Backstop for a barrier that never completes.
601    #[serde(default)]
602    pub timeout_sec: Option<i64>,
603    /// Receiving agents.
604    pub to: Vec<String>,
605}
606
607/// The factory drawn as a graph.
608#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
609pub struct RouteMap {
610    /// Which file it was generated from.
611    #[serde(default)]
612    pub config_path: Option<String>,
613    /// When the configuration was read.
614    pub generated_at: String,
615    /// Mermaid `flowchart` source, ready to render.
616    pub mermaid: String,
617}
618
619/// One supervised CLI execution.
620#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
621pub struct Run {
622    /// Which agent was run.
623    pub agent: String,
624    /// What the run could not get past, when it asked for help. Independent of `status`: an
625    /// agent can finish its task and still have been unable to check something. Without it a
626    /// blocked run looks exactly like a clean one on a list.
627    #[serde(default)]
628    pub blocked_on: Option<String>,
629    /// Where `cost_usd` came from.
630    pub cost_source: CostSource,
631    /// Null when the runner reported no cost, which the Tower logs loudly.
632    #[serde(default)]
633    pub cost_usd: Option<f64>,
634    /// Why the run ended, for the outcomes where that is not self-evident.
635    #[serde(default)]
636    pub detail: Option<String>,
637    /// How long the run took. Null while it is still going, and also null if the clock moved
638    /// backwards between the two readings — a negative duration on a dashboard is worse than
639    /// an absent one, because somebody will average it.
640    #[serde(default)]
641    pub duration_sec: Option<i64>,
642    /// Process exit code, when there was one.
643    #[serde(default)]
644    pub exit_code: Option<i32>,
645    /// When the process exited; null while it is still running.
646    #[serde(default)]
647    pub finished_at: Option<String>,
648    /// Hops left when this run was started, mirrored for display only.
649    #[serde(default)]
650    pub hops_remaining: Option<i32>,
651    /// The chain this run belongs to.
652    pub itinerary_id: String,
653    /// Which model the runner used, when it said.
654    #[serde(default)]
655    pub model: Option<String>,
656    /// The pipeline that started this run's chain, when one did.
657    #[serde(default)]
658    pub pipeline: Option<String>,
659    /// Identifier of this run.
660    pub run_id: String,
661    /// When the process was spawned.
662    pub started_at: String,
663    /// How it ended, or that it has not.
664    pub status: RunStatus,
665}
666
667/// Matching runs, newest first.
668#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
669pub struct RunList {
670    /// The runs.
671    pub runs: Vec<Run>,
672}
673
674/// How a run ended, or that it has not.
675///
676/// `halted` is deliberately distinct from `failed`: a rail stopping work — Hops, Fuel, the
677/// run cap or the Reserve — is the system doing its job, and colouring it like a crash
678/// teaches people to ignore the colour. `interrupted` means the run was alive when the Tower
679/// went away; it is recoverable, and recovery starts a new run rather than resuming this one.
680///
681/// There is no `stalled` here. Stalling is something an *itinerary* does when it parks at a
682/// barrier that never releases; a run either finishes or does not.
683#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
684pub enum RunStatus {
685    /// `running`
686    #[serde(rename = "running")]
687    Running,
688    /// `succeeded`
689    #[serde(rename = "succeeded")]
690    Succeeded,
691    /// `failed`
692    #[serde(rename = "failed")]
693    Failed,
694    /// `timed_out`
695    #[serde(rename = "timed_out")]
696    TimedOut,
697    /// `halted`
698    #[serde(rename = "halted")]
699    Halted,
700    /// `interrupted`
701    #[serde(rename = "interrupted")]
702    Interrupted,
703}
704
705/// Exactly one of `pipeline` and `to` must be given, and flags are only accepted alongside a
706/// pipeline, because a pipeline is what declares them.
707///
708/// Neither is marked required here because JSON Schema cannot state "exactly one of these"
709/// in a way this generator supports. The Tower enforces it and answers `400` — so a client
710/// that satisfies this schema can still be rejected.
711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
712pub struct SendFlightRequest {
713    /// What the receiving agent is being asked to do.
714    pub body: String,
715    /// Flag overrides, keyed by flag name. Unset flags take their declared default.
716    #[serde(default)]
717    pub flags: Option<std::collections::BTreeMap<String, bool>>,
718    /// Name of the pipeline to start.
719    #[serde(default)]
720    pub pipeline: Option<String>,
721    /// Name of an `entry = true` agent to send to directly.
722    #[serde(default)]
723    pub to: Option<String>,
724}
725
726/// Liveness of the Tower itself.
727#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
728pub enum Status {
729    /// `ok`
730    #[serde(rename = "ok")]
731    Ok,
732}
733
734/// Tokens consumed. Cached reads and writes are counted apart from fresh input because
735/// providers price them very differently, and blending them makes any derived cost wrong by
736/// whatever the cache hit rate happened to be.
737#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
738pub struct TokenUsage {
739    /// Tokens served from the provider's prompt cache.
740    pub cache_read: i64,
741    /// Tokens written into the provider's prompt cache.
742    pub cache_write: i64,
743    /// Prompt tokens billed at the input rate.
744    pub input: i64,
745    /// Generated tokens.
746    pub output: i64,
747}
748
749/// What starts a pipeline.
750#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
751pub struct Trigger {
752    /// Set when the pipeline fires on a five-field cron expression.
753    #[serde(default)]
754    pub cron: Option<String>,
755    /// Set when the pipeline fires on a fixed interval.
756    #[serde(default)]
757    pub every_seconds: Option<i64>,
758    /// Whether a human or a clock starts this pipeline.
759    pub kind: TriggerKind,
760}
761
762/// Whether a human or a clock starts a pipeline.
763#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
764pub enum TriggerKind {
765    /// `manual`
766    #[serde(rename = "manual")]
767    Manual,
768    /// `scheduled`
769    #[serde(rename = "scheduled")]
770    Scheduled,
771}
772
773/// A resolved window, and an honest account of how it was arrived at.
774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
775pub struct WindowSpan {
776    /// True when the start was reckoned against a local midnight.
777    pub calendar: bool,
778    /// When the period ends, which is the moment it was resolved.
779    pub end: String,
780    /// A human label, such as "Last 30 days".
781    pub label: String,
782    /// When the period begins. Null means from the beginning of what is kept.
783    #[serde(default)]
784    pub start: Option<String>,
785    /// True when the period reaches further back than retention keeps, which makes the
786    /// totals a lower bound rather than a total.
787    pub truncated: bool,
788    /// Which window this is.
789    pub window: CostWindow,
790    /// The IANA zone the start was reckoned in, or null for a rolling window. The absence is
791    /// the point: nobody should have to wonder which zone "last 7 days" used.
792    #[serde(default)]
793    pub zone: Option<String>,
794}
795
796/// Whether instances of one pipeline share a working directory or get one each. A schedule
797/// fires whether or not the last instance finished, so anything reaching a read-write agent
798/// wants its own.
799#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
800pub enum Workspace {
801    /// `shared`
802    #[serde(rename = "shared")]
803    Shared,
804    /// `per-itinerary`
805    #[serde(rename = "per-itinerary")]
806    PerItinerary,
807}
808
809/// query parameters for `getCosts`.
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
811pub struct GetCostsQuery {
812    /// The period to total over. Defaults to the last 30 days.
813    ///
814    /// Note that `today` and `month_to_date` are *calendar* windows and the rest are *rolling* ones.
815    /// A rolling window is the same length everywhere; a calendar one begins at local
816    /// midnight and therefore depends on where the Tower is standing. The response says
817    /// which zone was used, so the figure can be read without having to guess.
818    #[serde(default)]
819    pub window: Option<CostWindow>,
820    /// Narrow the totals, the per-agent and the per-model breakdowns to one workflow.
821    ///
822    /// `reserve` is deliberately unaffected. The Reserve caps the whole factory, so reporting
823    /// it against one workflow's spend would describe a rail that does not exist.
824    #[serde(default)]
825    pub pipeline: Option<String>,
826}
827
828/// path parameters for `cancelFlight`.
829#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
830pub struct CancelFlightPath {
831    /// Identifier of the queued flight.
832    pub flight_id: String,
833}
834
835/// query parameters for `getGraph`.
836#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
837pub struct GetGraphQuery {
838    /// Draw only what this pipeline sets in motion. Omit for the whole factory.
839    ///
840    /// A factory holds several pipelines and they are genuinely separate workflows — a
841    /// nightly sweep has nothing to do with taking a work item to a pull request. Drawn
842    /// together they read as one very confused process. An agent belonging to two workflows
843    /// appears in both, which is the honest answer.
844    #[serde(default)]
845    pub pipeline: Option<String>,
846}
847
848/// query parameters for `listHelp`.
849#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
850pub struct ListHelpQuery {
851    /// Only requests from this agent.
852    #[serde(default)]
853    pub agent: Option<String>,
854    /// Only requests raised by a run of this workflow.
855    #[serde(default)]
856    pub pipeline: Option<String>,
857    /// Only requests of this kind.
858    #[serde(default)]
859    pub blocker: Option<Blocker>,
860    /// Only requests nobody has dealt with yet.
861    #[serde(default)]
862    pub open: Option<bool>,
863    /// How far back to look. Defaults to the last 30 days.
864    #[serde(default)]
865    pub window: Option<CostWindow>,
866}
867
868/// query parameters for `listItineraries`.
869#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
870pub struct ListItinerariesQuery {
871    /// How far back to look. Defaults to the last 24 hours, because a dashboard opening on
872    /// ninety days of chains is answering a question nobody asked.
873    #[serde(default)]
874    pub window: Option<CostWindow>,
875    /// Only chains in this state.
876    #[serde(default)]
877    pub state: Option<ItineraryState>,
878}
879
880/// query parameters for `listLearnings`.
881#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
882pub struct ListLearningsQuery {
883    /// Only learnings belonging to this agent.
884    #[serde(default)]
885    pub agent: Option<String>,
886    /// Only learnings in this state.
887    #[serde(default)]
888    pub state: Option<LearningState>,
889}
890
891/// path parameters for `judgeLearning`.
892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
893pub struct JudgeLearningPath {
894    /// Identifier of the learning.
895    pub learning_id: String,
896}
897
898/// query parameters for `listRuns`.
899#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
900pub struct ListRunsQuery {
901    /// Return only runs in this state.
902    #[serde(default)]
903    pub status: Option<RunStatus>,
904    /// Return only runs belonging to this itinerary.
905    #[serde(default)]
906    pub itinerary_id: Option<String>,
907    /// Return only runs of this agent.
908    #[serde(default)]
909    pub agent: Option<String>,
910    /// Return only runs whose chain was started by this pipeline.
911    #[serde(default)]
912    pub pipeline: Option<String>,
913    /// How far back to look. Defaults to the last 24 hours, because a dashboard opening on
914    /// ninety days of history is answering a question nobody asked.
915    #[serde(default)]
916    pub window: Option<CostWindow>,
917    /// Maximum number of runs to return, newest first.
918    #[serde(default)]
919    pub limit: Option<i32>,
920}
921
922/// path parameters for `getRun`.
923#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
924pub struct GetRunPath {
925    /// Identifier of the run to fetch.
926    pub run_id: String,
927}
928
929/// path parameters for `getReport`.
930#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
931pub struct GetReportPath {
932    /// Identifier of the run.
933    pub run_id: String,
934}
935
936/// path parameters for `streamRun`.
937#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
938pub struct StreamRunPath {
939    /// Identifier of the run to stream.
940    pub run_id: String,
941}
942
943/// Everything the Tower must implement to serve this API.
944///
945/// One method per `operationId`. Adding an operation to `api/openapi.yaml` adds a method
946/// here, so an unimplemented endpoint is a compile error rather than a 404 discovered in
947/// production.
948pub trait Api: Send + Sync + 'static {
949    /// Every configured agent and the route map between them.
950    ///
951    /// This is the UI's graph view, and it is also how a human answers "what can talk to what"
952    /// without reading the TOML.
953    ///
954    /// `GET /agents`
955    fn list_agents(&self) -> impl core::future::Future<Output = Result<AgentList, Problem>> + Send;
956    /// What the factory has spent, and how much of that is measured.
957    ///
958    /// A single Fuel figure says whether a chain may continue. This says where the money went —
959    /// per agent, per model — and, critically, how much of the total the runners actually
960    /// reported rather than Layover inferring it from a rate card.
961    ///
962    /// Treat `confidence` as part of the number. A total that is mostly measured is still not
963    /// measured, so any estimated or unreported run downgrades the whole figure.
964    ///
965    /// `GET /costs`
966    fn get_costs(
967        &self,
968        query: GetCostsQuery,
969    ) -> impl core::future::Future<Output = Result<CostReport, Problem>> + Send;
970    /// Flights waiting for something to dispatch them.
971    ///
972    /// Work that has been asked for and not yet started. Until the supervisor exists this is
973    /// everything anybody has triggered; afterwards it is the backlog.
974    ///
975    /// `GET /flights`
976    fn list_pending(
977        &self,
978    ) -> impl core::future::Future<Output = Result<PendingList, Problem>> + Send;
979    /// Start work by sending the first flight of a new itinerary.
980    ///
981    /// Give either a `pipeline` or a `to`. A pipeline is the normal way in: it names the entry
982    /// agent and declares which flags may be set. A bare `to` sends to an agent marked
983    /// `entry = true` and accepts no flags.
984    ///
985    /// **The flight is queued, not run.** Dispatching it needs the supervisor, which is not part
986    /// of this release, so `202` means the work is booked and durable — it will start when there
987    /// is something to start it. `GET /flights` shows what is waiting.
988    ///
989    /// `POST /flights`
990    fn send_flight(
991        &self,
992        body: SendFlightRequest,
993    ) -> impl core::future::Future<Output = Result<FlightAccepted, Problem>> + Send;
994    /// Take a queued flight back off the queue.
995    ///
996    /// Only work that has not started can be cancelled. A run that is already going is stopped
997    /// with a Ground Stop, which is a different decision with a different blast radius — one
998    /// flight versus the whole factory — and conflating them would make the smaller action feel
999    /// as dangerous as the larger one.
1000    ///
1001    /// `DELETE /flights/{flight_id}`
1002    fn cancel_flight(
1003        &self,
1004        path: CancelFlightPath,
1005    ) -> impl core::future::Future<Output = Result<PendingList, Problem>> + Send;
1006    /// The route map as a diagram, with what is happening drawn on it.
1007    ///
1008    /// Mermaid source, generated from the configuration as it is on disk right now. Edit
1009    /// `layover.toml` and reload; the diagram changes with it, because nothing here is baked at
1010    /// build time.
1011    ///
1012    /// Agents currently running, waiting at a barrier, or freshly failed are coloured. An edge
1013    /// into a joined agent from a sender the barrier does not name is drawn as bypassing it,
1014    /// which is what actually happens.
1015    ///
1016    /// `GET /graph`
1017    fn get_graph(
1018        &self,
1019        query: GetGraphQuery,
1020    ) -> impl core::future::Future<Output = Result<RouteMap, Problem>> + Send;
1021    /// Halt everything.
1022    ///
1023    /// Ground Stop is a file on disk rather than in-memory state, so it survives a Tower crash
1024    /// and can be set by hand when nothing else is responding.
1025    ///
1026    /// `POST /ground-stop`
1027    fn engage_ground_stop(
1028        &self,
1029    ) -> impl core::future::Future<Output = Result<GroundStop, Problem>> + Send;
1030    /// Resume.
1031    ///
1032    /// `DELETE /ground-stop`
1033    fn release_ground_stop(
1034        &self,
1035    ) -> impl core::future::Future<Output = Result<GroundStop, Problem>> + Send;
1036    /// Liveness, version and whether a Ground Stop is engaged.
1037    ///
1038    /// `GET /health`
1039    fn get_health(&self) -> impl core::future::Future<Output = Result<Health, Problem>> + Send;
1040    /// What agents are stuck on.
1041    ///
1042    /// A lights-out factory's worst failure is not a crash — a crash is loud. It is an agent that
1043    /// quietly cannot do what it was asked, produces something plausible anyway, and passes it
1044    /// downstream. This is the channel that stops that being invisible.
1045    ///
1046    /// Measured against a working prototype, five of six requests were permission or access
1047    /// failures, so `access` is worth filtering for first.
1048    ///
1049    /// `GET /help`
1050    fn list_help(
1051        &self,
1052        query: ListHelpQuery,
1053    ) -> impl core::future::Future<Output = Result<HelpList, Problem>> + Send;
1054    /// Mark help requests as dealt with.
1055    ///
1056    /// Resolving says *the blocker is gone*, not *I have read this*. An agent that raises the same
1057    /// problem on its next run will raise it again, which is the point: a list that clears itself
1058    /// on being looked at stops being evidence of anything.
1059    ///
1060    /// Narrow by agent, blocker or run. An empty body resolves every open request in the window,
1061    /// which is what you want after fixing something that stopped everything.
1062    ///
1063    /// `POST /help/resolve`
1064    fn resolve_help(
1065        &self,
1066        body: ResolveHelpRequest,
1067    ) -> impl core::future::Future<Output = Result<HelpResolved, Problem>> + Send;
1068    /// Chains of work, and what became of each.
1069    ///
1070    /// A run is one agent doing one thing; an itinerary is the whole causal chain and the budget
1071    /// it shares. The distinction matters most when something goes wrong: a chain can be *stalled*
1072    /// — every run in it succeeded and nothing will ever happen again — and a list of runs cannot
1073    /// show that, because there is no failed run to point at.
1074    ///
1075    /// `GET /itineraries`
1076    fn list_itineraries(
1077        &self,
1078        query: ListItinerariesQuery,
1079    ) -> impl core::future::Future<Output = Result<ItineraryList, Problem>> + Send;
1080    /// What agents have worked out, and how well established it is.
1081    ///
1082    /// A learning applies as soon as it is proposed and expires unless later runs arrive at it
1083    /// independently. There is no approval queue, deliberately: a sibling project built one and
1084    /// after 22 days held 88 learnings, none ever approved, so not one had ever reached a run.
1085    ///
1086    /// A learning becomes permanent through independent rediscovery, which is evidence, rather
1087    /// than through its `impact` rating, which is the agent's own claim about its own work.
1088    ///
1089    /// `GET /learnings`
1090    fn list_learnings(
1091        &self,
1092        query: ListLearningsQuery,
1093    ) -> impl core::future::Future<Output = Result<LearningList, Problem>> + Send;
1094    /// Settle a learning, either way.
1095    ///
1096    /// **This is not an approval queue.** A learning applies from the moment it is proposed, and
1097    /// nothing is waiting on you. A sibling project gated learnings behind approval and after 22
1098    /// days held 88 of them, none ever approved, so not one had ever reached a run.
1099    ///
1100    /// This is the override. `confirmed` means "this is real, keep it indefinitely" and spares it
1101    /// from lapsing. `rejected` means "this is wrong, stop applying it" and takes it out of every
1102    /// future run. Both are judgements a person makes about something already in use, not
1103    /// permission for it to start being used.
1104    ///
1105    /// `PATCH /learnings/{learning_id}`
1106    fn judge_learning(
1107        &self,
1108        path: JudgeLearningPath,
1109        body: JudgeLearningRequest,
1110    ) -> impl core::future::Future<Output = Result<Learning, Problem>> + Send;
1111    /// Every declared pipeline, its trigger and the flags it accepts.
1112    ///
1113    /// `GET /pipelines`
1114    fn list_pipelines(
1115        &self,
1116    ) -> impl core::future::Future<Output = Result<PipelineList, Problem>> + Send;
1117    /// Runs, live and historical.
1118    ///
1119    /// `GET /runs`
1120    fn list_runs(
1121        &self,
1122        query: ListRunsQuery,
1123    ) -> impl core::future::Future<Output = Result<RunList, Problem>> + Send;
1124    /// One run, including how it ended.
1125    ///
1126    /// `GET /runs/{run_id}`
1127    fn get_run(
1128        &self,
1129        path: GetRunPath,
1130    ) -> impl core::future::Future<Output = Result<Run, Problem>> + Send;
1131    /// What the agent wrote about this run.
1132    ///
1133    /// A transcript is not a report: it contains every approach the agent abandoned, and reading
1134    /// one to find out what happened is slower than doing the work again. This is the agent''s own
1135    /// account of what it concluded.
1136    ///
1137    /// `GET /runs/{run_id}/report`
1138    fn get_report(
1139        &self,
1140        path: GetReportPath,
1141    ) -> impl core::future::Future<Output = Result<Report, Problem>> + Send;
1142    /// Live output from a run, as server-sent events.
1143    ///
1144    /// `GET /runs/{run_id}/stream`
1145    fn stream_run(
1146        &self,
1147        path: StreamRunPath,
1148    ) -> impl core::future::Future<Output = Result<EventStream, Problem>> + Send;
1149}
1150
1151/// Builds the axum router for this API.
1152///
1153/// Routes come straight from the specification, so a path can only exist here if it
1154/// exists there.
1155pub fn router<A: Api>(api: std::sync::Arc<A>) -> axum::Router {
1156    axum::Router::new()
1157        .route("/agents", axum::routing::get(handle_list_agents::<A>))
1158        .route("/costs", axum::routing::get(handle_get_costs::<A>))
1159        .route(
1160            "/flights",
1161            axum::routing::get(handle_list_pending::<A>).post(handle_send_flight::<A>),
1162        )
1163        .route(
1164            "/flights/{flight_id}",
1165            axum::routing::delete(handle_cancel_flight::<A>),
1166        )
1167        .route("/graph", axum::routing::get(handle_get_graph::<A>))
1168        .route(
1169            "/ground-stop",
1170            axum::routing::post(handle_engage_ground_stop::<A>)
1171                .delete(handle_release_ground_stop::<A>),
1172        )
1173        .route("/health", axum::routing::get(handle_get_health::<A>))
1174        .route("/help", axum::routing::get(handle_list_help::<A>))
1175        .route(
1176            "/help/resolve",
1177            axum::routing::post(handle_resolve_help::<A>),
1178        )
1179        .route(
1180            "/itineraries",
1181            axum::routing::get(handle_list_itineraries::<A>),
1182        )
1183        .route("/learnings", axum::routing::get(handle_list_learnings::<A>))
1184        .route(
1185            "/learnings/{learning_id}",
1186            axum::routing::patch(handle_judge_learning::<A>),
1187        )
1188        .route("/pipelines", axum::routing::get(handle_list_pipelines::<A>))
1189        .route("/runs", axum::routing::get(handle_list_runs::<A>))
1190        .route("/runs/{run_id}", axum::routing::get(handle_get_run::<A>))
1191        .route(
1192            "/runs/{run_id}/report",
1193            axum::routing::get(handle_get_report::<A>),
1194        )
1195        .route(
1196            "/runs/{run_id}/stream",
1197            axum::routing::get(handle_stream_run::<A>),
1198        )
1199        .with_state(api)
1200}
1201
1202async fn handle_list_agents<A: Api>(
1203    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1204) -> axum::response::Response {
1205    match api.list_agents().await {
1206        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1207        Err(problem) => problem.into_response(),
1208    }
1209}
1210
1211async fn handle_get_costs<A: Api>(
1212    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1213    axum::extract::Query(query): axum::extract::Query<GetCostsQuery>,
1214) -> axum::response::Response {
1215    match api.get_costs(query).await {
1216        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1217        Err(problem) => problem.into_response(),
1218    }
1219}
1220
1221async fn handle_list_pending<A: Api>(
1222    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1223) -> axum::response::Response {
1224    match api.list_pending().await {
1225        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1226        Err(problem) => problem.into_response(),
1227    }
1228}
1229
1230async fn handle_send_flight<A: Api>(
1231    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1232    axum::Json(body): axum::Json<SendFlightRequest>,
1233) -> axum::response::Response {
1234    match api.send_flight(body).await {
1235        Ok(value) => (axum::http::StatusCode::ACCEPTED, axum::Json(value)).into_response(),
1236        Err(problem) => problem.into_response(),
1237    }
1238}
1239
1240async fn handle_cancel_flight<A: Api>(
1241    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1242    axum::extract::Path(path): axum::extract::Path<CancelFlightPath>,
1243) -> axum::response::Response {
1244    match api.cancel_flight(path).await {
1245        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1246        Err(problem) => problem.into_response(),
1247    }
1248}
1249
1250async fn handle_get_graph<A: Api>(
1251    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1252    axum::extract::Query(query): axum::extract::Query<GetGraphQuery>,
1253) -> axum::response::Response {
1254    match api.get_graph(query).await {
1255        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1256        Err(problem) => problem.into_response(),
1257    }
1258}
1259
1260async fn handle_engage_ground_stop<A: Api>(
1261    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1262) -> axum::response::Response {
1263    match api.engage_ground_stop().await {
1264        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1265        Err(problem) => problem.into_response(),
1266    }
1267}
1268
1269async fn handle_release_ground_stop<A: Api>(
1270    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1271) -> axum::response::Response {
1272    match api.release_ground_stop().await {
1273        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1274        Err(problem) => problem.into_response(),
1275    }
1276}
1277
1278async fn handle_get_health<A: Api>(
1279    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1280) -> axum::response::Response {
1281    match api.get_health().await {
1282        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1283        Err(problem) => problem.into_response(),
1284    }
1285}
1286
1287async fn handle_list_help<A: Api>(
1288    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1289    axum::extract::Query(query): axum::extract::Query<ListHelpQuery>,
1290) -> axum::response::Response {
1291    match api.list_help(query).await {
1292        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1293        Err(problem) => problem.into_response(),
1294    }
1295}
1296
1297async fn handle_resolve_help<A: Api>(
1298    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1299    axum::Json(body): axum::Json<ResolveHelpRequest>,
1300) -> axum::response::Response {
1301    match api.resolve_help(body).await {
1302        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1303        Err(problem) => problem.into_response(),
1304    }
1305}
1306
1307async fn handle_list_itineraries<A: Api>(
1308    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1309    axum::extract::Query(query): axum::extract::Query<ListItinerariesQuery>,
1310) -> axum::response::Response {
1311    match api.list_itineraries(query).await {
1312        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1313        Err(problem) => problem.into_response(),
1314    }
1315}
1316
1317async fn handle_list_learnings<A: Api>(
1318    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1319    axum::extract::Query(query): axum::extract::Query<ListLearningsQuery>,
1320) -> axum::response::Response {
1321    match api.list_learnings(query).await {
1322        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1323        Err(problem) => problem.into_response(),
1324    }
1325}
1326
1327async fn handle_judge_learning<A: Api>(
1328    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1329    axum::extract::Path(path): axum::extract::Path<JudgeLearningPath>,
1330    axum::Json(body): axum::Json<JudgeLearningRequest>,
1331) -> axum::response::Response {
1332    match api.judge_learning(path, body).await {
1333        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1334        Err(problem) => problem.into_response(),
1335    }
1336}
1337
1338async fn handle_list_pipelines<A: Api>(
1339    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1340) -> axum::response::Response {
1341    match api.list_pipelines().await {
1342        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1343        Err(problem) => problem.into_response(),
1344    }
1345}
1346
1347async fn handle_list_runs<A: Api>(
1348    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1349    axum::extract::Query(query): axum::extract::Query<ListRunsQuery>,
1350) -> axum::response::Response {
1351    match api.list_runs(query).await {
1352        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1353        Err(problem) => problem.into_response(),
1354    }
1355}
1356
1357async fn handle_get_run<A: Api>(
1358    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1359    axum::extract::Path(path): axum::extract::Path<GetRunPath>,
1360) -> axum::response::Response {
1361    match api.get_run(path).await {
1362        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1363        Err(problem) => problem.into_response(),
1364    }
1365}
1366
1367async fn handle_get_report<A: Api>(
1368    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1369    axum::extract::Path(path): axum::extract::Path<GetReportPath>,
1370) -> axum::response::Response {
1371    match api.get_report(path).await {
1372        Ok(value) => (axum::http::StatusCode::OK, axum::Json(value)).into_response(),
1373        Err(problem) => problem.into_response(),
1374    }
1375}
1376
1377async fn handle_stream_run<A: Api>(
1378    axum::extract::State(api): axum::extract::State<std::sync::Arc<A>>,
1379    axum::extract::Path(path): axum::extract::Path<StreamRunPath>,
1380) -> axum::response::Response {
1381    match api.stream_run(path).await {
1382        Ok(stream) => stream.into_response(),
1383        Err(problem) => problem.into_response(),
1384    }
1385}
1386
1387/// Every operation the specification declares, as (method, path, operationId).
1388///
1389/// Exposed so tests can assert the router and the specification agree.
1390pub const OPERATIONS: [(&str, &str, &str); 19] = [
1391    ("GET", "/agents", "listAgents"),
1392    ("GET", "/costs", "getCosts"),
1393    ("GET", "/flights", "listPending"),
1394    ("POST", "/flights", "sendFlight"),
1395    ("DELETE", "/flights/{flight_id}", "cancelFlight"),
1396    ("GET", "/graph", "getGraph"),
1397    ("POST", "/ground-stop", "engageGroundStop"),
1398    ("DELETE", "/ground-stop", "releaseGroundStop"),
1399    ("GET", "/health", "getHealth"),
1400    ("GET", "/help", "listHelp"),
1401    ("POST", "/help/resolve", "resolveHelp"),
1402    ("GET", "/itineraries", "listItineraries"),
1403    ("GET", "/learnings", "listLearnings"),
1404    ("PATCH", "/learnings/{learning_id}", "judgeLearning"),
1405    ("GET", "/pipelines", "listPipelines"),
1406    ("GET", "/runs", "listRuns"),
1407    ("GET", "/runs/{run_id}", "getRun"),
1408    ("GET", "/runs/{run_id}/report", "getReport"),
1409    ("GET", "/runs/{run_id}/stream", "streamRun"),
1410];