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];