Skip to main content

inferlab_protocol/
wire.rs

1//! The versioned wire types of the framework integration protocol
2//! ([[RFC-0006:C-INTEGRATIONS]]): the one-shot stdin/stdout JSON contract for
3//! the plan/render serve operations, plus the client request/result surfaces
4//! the release-owned Eval and Bench measurement runtimes exchange with their
5//! clients. [`AdapterProtocol`] is the schema root from which the committed
6//! JSON schema and the Python SDK models are generated.
7
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::path::PathBuf;
12
13/// The shared protocol version used by framework integrations and release-owned
14/// measurement clients. The only accepted value is `7` (serialized as the
15/// string `"7"`); a mismatch is rejected before lowering.
16#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
17pub enum ProtocolVersion {
18    /// Protocol version 7.
19    #[serde(rename = "7")]
20    V7,
21}
22
23/// The one JSON request an integration reads from stdin, tagged by the
24/// requested operation.
25#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
26#[serde(tag = "operation", rename_all = "snake_case", deny_unknown_fields)]
27pub enum AdapterRequest {
28    /// Plan a serve topology: declare roles, per-replica requirements, links,
29    /// and endpoint requirements from the requested shape.
30    PlanServe {
31        protocol_version: ProtocolVersion,
32        input: PlanServeInput,
33    },
34    /// Render final process invocations for a planned topology, given the
35    /// control plane's concrete process allocations.
36    RenderServe {
37        protocol_version: ProtocolVersion,
38        input: RenderServeInput,
39    },
40}
41
42impl AdapterRequest {
43    /// The protocol version carried by this request, regardless of operation.
44    #[must_use]
45    pub const fn protocol_version(&self) -> ProtocolVersion {
46        match self {
47            Self::PlanServe {
48                protocol_version, ..
49            }
50            | Self::RenderServe {
51                protocol_version, ..
52            } => *protocol_version,
53        }
54    }
55}
56
57/// The requested serve shape a `PlanServe` operation lowers into a topology.
58#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
59#[serde(deny_unknown_fields)]
60pub struct PlanServeInput {
61    pub model: ServeModelInput,
62    pub topology: ServeTopology,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub gateway_backend: Option<String>,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub pd_router_backend: Option<String>,
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub kv_transfer: Option<KvTransferMechanism>,
69    pub roles: Vec<ServeRoleInput>,
70    #[serde(default)]
71    pub profiling: bool,
72}
73
74/// The planned topology plus the control plane's concrete allocations that a
75/// `RenderServe` operation turns into final process invocations.
76#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
77#[serde(deny_unknown_fields)]
78pub struct RenderServeInput {
79    pub model: ServeModelInput,
80    pub topology: ServeTopology,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub gateway_backend: Option<String>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub pd_router_backend: Option<String>,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub kv_transfer: Option<KvTransferMechanism>,
87    pub allocations: Vec<ServeProcessAllocation>,
88    #[serde(default)]
89    pub profiling: bool,
90}
91
92/// The serving deployment topology.
93#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
94#[serde(rename_all = "snake_case")]
95pub enum ServeTopology {
96    /// One aggregated serving role.
97    Single,
98    /// Disaggregated prefill and decode roles.
99    PrefillDecode,
100}
101
102/// The logical role a replica plays in a serve topology.
103#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
104#[serde(rename_all = "snake_case")]
105pub enum ServeRoleKind {
106    /// Aggregated serving role of a single topology.
107    Serve,
108    /// Prefill role of a disaggregated topology.
109    Prefill,
110    /// Decode role of a disaggregated topology.
111    Decode,
112}
113
114/// The KV-transfer mechanism connecting prefill and decode.
115#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
116#[serde(rename_all = "snake_case")]
117pub enum KvTransferMechanism {
118    /// Mooncake KV-cache transfer.
119    Mooncake,
120    /// NIXL KV-cache transfer.
121    Nixl,
122}
123
124/// A requested serving role: its identity, kind, replica cardinality, and
125/// declared (not-yet-completed) parallelism and settings.
126#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
127#[serde(deny_unknown_fields)]
128pub struct ServeRoleInput {
129    pub id: String,
130    pub kind: ServeRoleKind,
131    pub replica_count: u32,
132    pub parallelism: Parallelism,
133    pub settings: BTreeMap<String, SettingValue>,
134}
135
136/// A role as the integration resolved it: preserved identity and cardinality
137/// with the complete effective settings and parallelism.
138#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
139#[serde(deny_unknown_fields)]
140pub struct ServeRoleResult {
141    pub id: String,
142    pub kind: ServeRoleKind,
143    pub declared_replica_count: u32,
144    pub effective_replica_count: u32,
145    pub effective_settings: BTreeMap<String, SettingValue>,
146    pub effective_parallelism: Parallelism,
147    /// The public endpoint contract for a direct `single` Engine. Gateway-
148    /// backed shapes leave this absent and carry the contract on Gateway.
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub public_endpoint: Option<EndpointRequirement>,
151    #[serde(default)]
152    pub render_inputs: Vec<RenderInputDeclaration>,
153}
154
155/// Framework-neutral component-aware parallelism ([[RFC-0003:C-SERVE-PARALLELISM]]).
156/// Every component is optional so an operator can override one without
157/// repeating the rest; omitted components are filled by the integration into a
158/// complete effective shape.
159#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
160#[serde(default, deny_unknown_fields)]
161pub struct Parallelism {
162    /// Outer deployment parallelism shared by attention and experts.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub outer: Option<ParallelismOuter>,
165    /// Attention-block parallelism.
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub attention: Option<ParallelismAttention>,
168    /// MoE expert parallelism.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub experts: Option<ParallelismExperts>,
171}
172
173impl Parallelism {
174    /// Overlay the components present in `other` onto `self`, leaving
175    /// components `other` omits untouched (the per-component precedence merge).
176    pub fn merge_from(&mut self, other: &Self) {
177        if let Some(other) = &other.outer {
178            self.outer.get_or_insert_default().merge_from(other);
179        }
180        if let Some(other) = &other.attention {
181            self.attention.get_or_insert_default().merge_from(other);
182        }
183        if let Some(other) = &other.experts {
184            self.experts.get_or_insert_default().merge_from(other);
185        }
186    }
187}
188
189/// Outer deployment parallelism: tensor and pipeline degrees.
190#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
191#[serde(default, deny_unknown_fields)]
192pub struct ParallelismOuter {
193    #[schemars(range(min = 1))]
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub tensor_parallel_size: Option<u32>,
196    #[schemars(range(min = 1))]
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub pipeline_parallel_size: Option<u32>,
199}
200
201impl ParallelismOuter {
202    fn merge_from(&mut self, other: &Self) {
203        if other.tensor_parallel_size.is_some() {
204            self.tensor_parallel_size = other.tensor_parallel_size;
205        }
206        if other.pipeline_parallel_size.is_some() {
207            self.pipeline_parallel_size = other.pipeline_parallel_size;
208        }
209    }
210}
211
212/// Attention-block parallelism: tensor, data, and context degrees.
213#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
214#[serde(default, deny_unknown_fields)]
215pub struct ParallelismAttention {
216    #[schemars(range(min = 1))]
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub tensor_parallel_size: Option<u32>,
219    #[schemars(range(min = 1))]
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub data_parallel_size: Option<u32>,
222    #[schemars(range(min = 1))]
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub context_parallel_size: Option<u32>,
225}
226
227impl ParallelismAttention {
228    fn merge_from(&mut self, other: &Self) {
229        if other.tensor_parallel_size.is_some() {
230            self.tensor_parallel_size = other.tensor_parallel_size;
231        }
232        if other.data_parallel_size.is_some() {
233            self.data_parallel_size = other.data_parallel_size;
234        }
235        if other.context_parallel_size.is_some() {
236            self.context_parallel_size = other.context_parallel_size;
237        }
238    }
239}
240
241/// MoE expert parallelism: tensor, data, expert, and dense-tensor degrees.
242#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
243#[serde(default, deny_unknown_fields)]
244pub struct ParallelismExperts {
245    #[schemars(range(min = 1))]
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub tensor_parallel_size: Option<u32>,
248    #[schemars(range(min = 1))]
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub data_parallel_size: Option<u32>,
251    #[schemars(range(min = 1))]
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub expert_parallel_size: Option<u32>,
254    #[schemars(range(min = 1))]
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub dense_tensor_parallel_size: Option<u32>,
257}
258
259impl ParallelismExperts {
260    fn merge_from(&mut self, other: &Self) {
261        if other.tensor_parallel_size.is_some() {
262            self.tensor_parallel_size = other.tensor_parallel_size;
263        }
264        if other.data_parallel_size.is_some() {
265            self.data_parallel_size = other.data_parallel_size;
266        }
267        if other.expert_parallel_size.is_some() {
268            self.expert_parallel_size = other.expert_parallel_size;
269        }
270        if other.dense_tensor_parallel_size.is_some() {
271            self.dense_tensor_parallel_size = other.dense_tensor_parallel_size;
272        }
273    }
274}
275
276/// The logical model supplied during serving planning and rendering.
277#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
278#[serde(deny_unknown_fields)]
279pub struct ServeModelInput {
280    pub id: String,
281    pub served_name: String,
282}
283
284/// The model identity used by measurement clients. Unlike integration
285/// planning, a benchmark client may need a controller-visible tokenizer
286/// locator.
287#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
288#[serde(deny_unknown_fields)]
289pub struct MeasurementModelInput {
290    pub locator: String,
291    pub served_name: String,
292}
293
294/// A concrete host/port endpoint the control plane allocated for a process.
295#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
296#[serde(deny_unknown_fields)]
297pub struct EndpointAssignment {
298    pub host: String,
299    pub port: u16,
300}
301
302/// The public workload endpoint an Eval or Bench client connects to.
303#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
304#[serde(deny_unknown_fields)]
305pub struct ClientEndpointInput {
306    pub protocol: EndpointProtocol,
307    pub host: String,
308    pub port: u16,
309    pub completions_path: String,
310    pub chat_completions_path: String,
311}
312
313/// The measurement an Eval client runs against the workload endpoint.
314#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
315#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
316pub enum EvalDefinitionInput {
317    /// A single-prompt liveness check.
318    #[serde(rename = "openai_smoke")]
319    OpenAiSmoke {
320        prompt: String,
321        max_tokens: u32,
322        timeout_seconds: u64,
323    },
324    /// An lm-eval task run with a pass threshold on the chosen metric.
325    LmEval {
326        task: Box<EvalTaskSourceInput>,
327        #[serde(default)]
328        request_body: BTreeMap<String, SettingValue>,
329        limit: Option<u32>,
330        few_shot: Option<u32>,
331        seed: Option<u64>,
332        trials: u32,
333        max_tokens: Option<u32>,
334        concurrency: Option<u32>,
335        metric: String,
336        #[serde(default)]
337        metric_filter: Option<String>,
338        threshold: f64,
339        timeout_seconds: u64,
340    },
341}
342
343/// The resolved lm-eval task source consumed by the release-owned Eval runner.
344#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
345#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
346pub enum EvalTaskSourceInput {
347    /// One individual task shipped by the pinned lm-eval runtime.
348    BuiltIn { name: String },
349    /// One task closure shipped and identity-bound by the Inferlab release.
350    Bundled {
351        name: String,
352        task_identity: String,
353        path: PathBuf,
354        task_closure_sha256: String,
355        task_definition_sha256: String,
356        prompt_asset_sha256: String,
357        dataset_asset_sha256: String,
358        scorer_sha256: String,
359    },
360    /// One validated workspace-local lm-eval YAML file.
361    WorkspaceYaml { path: PathBuf },
362}
363
364/// The workload shape a Bench client drives, shared across its load cases.
365#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
366#[serde(deny_unknown_fields)]
367pub struct BenchDefinitionInput {
368    pub request_source: BenchRequestSourceInput,
369    pub seed: u64,
370    #[serde(default)]
371    pub request_body: BTreeMap<String, SettingValue>,
372    #[serde(default)]
373    pub request_slo: Option<BenchRequestSloInput>,
374    pub timeout_seconds: u64,
375    #[serde(default)]
376    pub reset_prefix_cache: bool,
377}
378
379/// One closed request origin lowered by Inferlab for the Bench runtime.
380#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
381#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
382pub enum BenchRequestSourceInput {
383    /// AIPerf generates exact token-shape prompts from the release-pinned
384    /// synthetic generator.
385    Random {
386        input_tokens: u32,
387        output_tokens: u32,
388    },
389    /// Inferlab materializes a release-catalog conversation snapshot before
390    /// AIPerf starts.
391    Dataset {
392        dataset: BenchDatasetInput,
393        max_input_tokens: u32,
394        output_tokens: Option<u32>,
395        catalog: BenchDatasetCatalogInput,
396    },
397}
398
399/// Release-qualified public Bench datasets.
400#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
401#[serde(rename_all = "snake_case")]
402pub enum BenchDatasetInput {
403    Sharegpt,
404}
405
406/// Immutable release-catalog facts resolved by the Rust control plane.
407#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
408#[serde(deny_unknown_fields)]
409pub struct BenchDatasetCatalogInput {
410    pub upstream_identity: String,
411    pub url: String,
412    pub sha256: String,
413    pub source_format: String,
414    pub license: String,
415    pub cache_path: PathBuf,
416    pub cache_state: BenchDatasetCacheState,
417    pub materialization_identity: String,
418}
419
420/// Read-only cache state observed while resolving the Bench plan.
421#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
422#[serde(rename_all = "snake_case")]
423pub enum BenchDatasetCacheState {
424    Missing,
425    Present,
426}
427
428/// One frozen dataset population consumed sequentially by every Bench case.
429#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
430#[serde(deny_unknown_fields)]
431pub struct BenchPopulationInput {
432    pub path: PathBuf,
433    pub sha256: String,
434    pub entries: u32,
435    pub tpot_applicable: bool,
436}
437
438/// The bounded tokenizer-backed operation that materializes one dataset
439/// population before any Bench case starts.
440#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
441#[serde(deny_unknown_fields)]
442pub struct BenchDatasetPreparationRequest {
443    pub protocol_version: ProtocolVersion,
444    pub model: MeasurementModelInput,
445    pub request_source: BenchRequestSourceInput,
446    pub source_path: PathBuf,
447    pub required_entries: u32,
448    pub seed: u64,
449    #[serde(default)]
450    pub request_body: BTreeMap<String, SettingValue>,
451    pub artifact_dir: PathBuf,
452}
453
454/// Summary of the realized token counts. Exact per-entry values remain in the
455/// population evidence artifact.
456#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
457#[serde(deny_unknown_fields)]
458pub struct BenchTokenCountSummary {
459    pub minimum: u32,
460    pub maximum: u32,
461    pub mean: f64,
462}
463
464/// Terminal result of dataset population materialization.
465#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
466#[serde(deny_unknown_fields)]
467pub struct BenchDatasetPreparationResult {
468    pub schema_version: u32,
469    pub status: ClientStatus,
470    pub materialization_identity: String,
471    pub requested_entries: u32,
472    pub candidate_entries: u64,
473    pub admitted_entries: u64,
474    pub ineligible_entries: u64,
475    #[serde(default)]
476    pub ineligible_reasons: BTreeMap<String, u64>,
477    pub population: Option<BenchPopulationInput>,
478    pub input_tokens: Option<BenchTokenCountSummary>,
479    pub output_tokens: Option<BenchTokenCountSummary>,
480    pub evidence_path: Option<PathBuf>,
481    pub error: Option<String>,
482}
483
484/// Per-request latency bounds lowered to the release-owned Bench runner.
485#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
486#[serde(deny_unknown_fields)]
487pub struct BenchRequestSloInput {
488    #[serde(default)]
489    pub request_latency_ms: Option<f64>,
490    #[serde(default)]
491    pub ttft_ms: Option<f64>,
492    #[serde(default)]
493    pub tpot_ms: Option<f64>,
494    pub minimum_good_request_ratio: f64,
495}
496
497/// A framework-specific server setting value carried as structured JSON data
498/// (never a pre-rendered shell fragment) across the integration boundary.
499#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
500#[serde(untagged)]
501pub enum SettingValue {
502    /// A JSON boolean.
503    Bool(bool),
504    /// A JSON integer.
505    Integer(i64),
506    /// A JSON floating-point number.
507    Float(f64),
508    /// A JSON string.
509    String(String),
510    /// A JSON array of setting values.
511    Array(Vec<SettingValue>),
512    /// A JSON object of named setting values.
513    Object(BTreeMap<String, SettingValue>),
514}
515
516/// The one JSON response an integration writes to stdout, tagged by outcome.
517#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
518#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
519pub enum AdapterResponse {
520    /// The operation succeeded and carries its result.
521    Ok {
522        protocol_version: ProtocolVersion,
523        result: Box<AdapterResult>,
524    },
525    /// The operation was rejected with a structured error.
526    Error {
527        protocol_version: ProtocolVersion,
528        error: AdapterError,
529    },
530}
531
532impl AdapterResponse {
533    /// The protocol version carried by this response, regardless of outcome.
534    #[must_use]
535    pub const fn protocol_version(&self) -> ProtocolVersion {
536        match self {
537            Self::Ok {
538                protocol_version, ..
539            }
540            | Self::Error {
541                protocol_version, ..
542            } => *protocol_version,
543        }
544    }
545}
546
547/// The successful result of an [`AdapterRequest`], tagged by the operation it
548/// answers.
549#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
550#[serde(tag = "operation", rename_all = "snake_case", deny_unknown_fields)]
551pub enum AdapterResult {
552    /// The planned topology from a `PlanServe` request.
553    PlanServe { output: Box<PlanServeResult> },
554    /// The rendered process invocations from a `RenderServe` request.
555    RenderServe { output: Box<RenderServeResult> },
556}
557
558/// The lowered topology returned by a `PlanServe`: effective Engine roles,
559/// whole-replica requirements, logical links, and separate optional Gateway
560/// and P/D Router component plans.
561#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
562#[serde(deny_unknown_fields)]
563pub struct PlanServeResult {
564    pub integration: IntegrationIdentity,
565    pub roles: Vec<ServeRoleResult>,
566    pub replicas: Vec<ServeReplicaRequirement>,
567    pub links: Vec<ServeRoleLink>,
568    #[serde(default, skip_serializing_if = "Option::is_none")]
569    pub gateway: Option<GatewayPlan>,
570    #[serde(default, skip_serializing_if = "Option::is_none")]
571    pub pd_router: Option<PdRouterPlan>,
572}
573
574/// Which bounded implementation renders an accepted concrete frontend
575/// allocation. This is a lowering boundary only; the control plane always
576/// owns placement, lifecycle, cleanup, endpoints, and records.
577#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
578#[serde(rename_all = "snake_case")]
579pub enum RenderSource {
580    ControlPlane,
581    Integration,
582}
583
584/// The one canonical process role available to a protocol-v7 frontend.
585#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
586#[serde(rename_all = "snake_case")]
587pub enum FrontendProcessRole {
588    Gateway,
589}
590
591/// The fixed co-rendering requirement shared by compatible frontend plans.
592#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
593#[serde(deny_unknown_fields)]
594pub struct FrontendCoRendering {
595    pub process_role: FrontendProcessRole,
596}
597
598/// The literal first member of every closed frontend component binding.
599#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
600#[serde(rename_all = "snake_case")]
601pub enum FrontendGatewayComponent {
602    Gateway,
603}
604
605/// The literal second member of the fused P/D frontend component binding.
606#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
607#[serde(rename_all = "snake_case")]
608pub enum FrontendPdRouterComponent {
609    PdRouter,
610}
611
612/// The stable schema branch for a Gateway-only frontend binding.
613#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
614#[serde(transparent)]
615pub struct GatewayFrontendBinding(pub [FrontendGatewayComponent; 1]);
616
617/// The stable schema branch for a fused Gateway and P/D Router binding.
618#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
619#[serde(transparent)]
620pub struct GatewayPdRouterFrontendBinding(
621    pub (FrontendGatewayComponent, FrontendPdRouterComponent),
622);
623
624/// The only two frontend bindings protocol v7 accepts. Tuple representation
625/// deliberately serializes as the closed ordered arrays `["gateway"]` and
626/// `["gateway", "pd_router"]` rather than as an open component list.
627#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
628#[serde(untagged)]
629pub enum FrontendComponents {
630    Gateway(GatewayFrontendBinding),
631    GatewayPdRouter(GatewayPdRouterFrontendBinding),
632}
633
634impl FrontendComponents {
635    #[must_use]
636    pub const fn gateway() -> Self {
637        Self::Gateway(GatewayFrontendBinding([FrontendGatewayComponent::Gateway]))
638    }
639
640    #[must_use]
641    pub const fn gateway_pd_router() -> Self {
642        Self::GatewayPdRouter(GatewayPdRouterFrontendBinding((
643            FrontendGatewayComponent::Gateway,
644            FrontendPdRouterComponent::PdRouter,
645        )))
646    }
647
648    #[must_use]
649    pub const fn includes_pd_router(&self) -> bool {
650        matches!(self, Self::GatewayPdRouter(_))
651    }
652}
653
654/// The target a Gateway forwards accepted public requests to.
655#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
656#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
657pub enum GatewayTarget {
658    /// A routed-single Gateway targets the sole Engine role entry point.
659    Engine { role: String },
660    /// A P/D Gateway hands requests to its co-rendered P/D Router component.
661    PdRouter,
662}
663
664/// The public frontend component plan returned independently from Engine and
665/// P/D Router planning.
666#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
667#[serde(deny_unknown_fields)]
668pub struct GatewayPlan {
669    pub backend: String,
670    pub implementation: String,
671    pub implementation_version: String,
672    pub effective_settings: BTreeMap<String, SettingValue>,
673    pub endpoint: EndpointRequirement,
674    pub readiness: ReadinessProbe,
675    #[serde(default)]
676    pub ports: Vec<String>,
677    pub targets: Vec<GatewayTarget>,
678    #[serde(default)]
679    pub render_inputs: Vec<RenderInputDeclaration>,
680    pub render_source: RenderSource,
681    pub co_rendering: FrontendCoRendering,
682}
683
684/// Independent policies for choosing prefill and decode targets.
685#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
686#[serde(deny_unknown_fields)]
687pub struct PdRoutingPolicies {
688    pub prefill: String,
689    pub decode: String,
690}
691
692/// The currently demonstrated Gateway-to-P/D Router handoff is internal to
693/// one fused frontend process.
694#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
695#[serde(rename_all = "snake_case")]
696pub enum FrontendHandoff {
697    InProcess,
698}
699
700/// The P/D orchestration component plan, kept separate even when the same
701/// process and provider also realize Gateway.
702#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
703#[serde(deny_unknown_fields)]
704pub struct PdRouterPlan {
705    pub backend: String,
706    pub implementation: String,
707    pub implementation_version: String,
708    pub effective_settings: BTreeMap<String, SettingValue>,
709    pub policies: PdRoutingPolicies,
710    pub prefill_role: String,
711    pub decode_role: String,
712    pub target_scheme: TargetEndpointScheme,
713    #[serde(default)]
714    pub ports: Vec<String>,
715    pub readiness: ReadinessProbe,
716    pub handoff: FrontendHandoff,
717    #[serde(default)]
718    pub render_inputs: Vec<RenderInputDeclaration>,
719    pub render_source: RenderSource,
720    pub co_rendering: FrontendCoRendering,
721}
722
723/// One workspace-authored UTF-8 source file an integration declares during
724/// planning for the control plane to supply during final rendering.
725#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
726#[serde(deny_unknown_fields)]
727pub struct RenderInputDeclaration {
728    pub source_path: String,
729}
730
731/// The original declared path plus the exact UTF-8 contents and digest the
732/// control plane supplies to an integration during final rendering.
733#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
734#[serde(deny_unknown_fields)]
735pub struct SuppliedRenderInput {
736    pub source_path: String,
737    pub text: String,
738    pub sha256: String,
739}
740
741/// A whole-replica resource and readiness requirement the integration declares
742/// without choosing placement, ranks, or concrete endpoints.
743#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
744#[serde(deny_unknown_fields)]
745pub struct ServeReplicaRequirement {
746    pub id: String,
747    pub role_id: String,
748    pub replica_index: u32,
749    pub device_count: u32,
750    pub ports: Vec<String>,
751    pub primary_ports: Vec<String>,
752    pub primary_readiness: ReadinessProbe,
753    pub worker_readiness: ReadinessProbe,
754    #[serde(default, skip_serializing_if = "Option::is_none")]
755    pub capture_target: Option<CaptureTargetRequirement>,
756}
757
758/// One concrete process allocation supplied to `RenderServe`. Model-rank and
759/// process-only frontend identities are distinct so a frontend cannot acquire
760/// model coordinates or a model locator by construction.
761#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
762#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
763pub enum ServeProcessAllocation {
764    ModelRank {
765        process: String,
766        role: String,
767        role_kind: ServeRoleKind,
768        replica: u32,
769        rank: u32,
770        rank_count: u32,
771        machine: String,
772        devices: Vec<u32>,
773        model_locator: String,
774        #[serde(default, skip_serializing_if = "Option::is_none")]
775        endpoint: Option<EndpointAssignment>,
776        ports: BTreeMap<String, EndpointAssignment>,
777        cache: String,
778        launch: AllocationLaunch,
779        effective_settings: BTreeMap<String, SettingValue>,
780        effective_parallelism: Parallelism,
781        #[serde(default)]
782        links: Vec<ServeRoleLink>,
783        #[serde(default)]
784        dependencies: Vec<String>,
785        #[serde(default)]
786        render_inputs: Vec<SuppliedRenderInput>,
787    },
788    Frontend {
789        process: String,
790        process_role: FrontendProcessRole,
791        components: FrontendComponents,
792        machine: String,
793        devices: Vec<u32>,
794        endpoint: EndpointAssignment,
795        ports: BTreeMap<String, EndpointAssignment>,
796        cache: String,
797        launch: AllocationLaunch,
798        gateway: Box<GatewayPlan>,
799        #[serde(default, skip_serializing_if = "Option::is_none")]
800        pd_router: Option<Box<PdRouterPlan>>,
801        #[serde(default)]
802        links: Vec<ServeRoleLink>,
803        #[serde(default)]
804        dependencies: Vec<String>,
805        #[serde(default)]
806        render_inputs: Vec<SuppliedRenderInput>,
807    },
808}
809
810/// The machine-local launch channel selected by the control plane.
811#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
812#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
813pub enum AllocationLaunch {
814    Local,
815    Ssh { target: String },
816}
817
818/// A directed link between serve roles the integration declares as part of the
819/// topology.
820#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
821#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
822pub enum ServeRoleLink {
823    /// The source role routes requests to the target roles.
824    RequestRouting {
825        source: String,
826        targets: Vec<String>,
827    },
828    /// KV cache is transferred from source to target over `mechanism`.
829    KvTransfer {
830        source: String,
831        target: String,
832        mechanism: KvTransferMechanism,
833    },
834    /// The source discovers the target through a bootstrap port.
835    Bootstrap {
836        source: String,
837        target: String,
838        port: String,
839    },
840    /// The source and target exchange out-of-band data over a side-channel port.
841    SideChannel {
842        source: String,
843        target: String,
844        port: String,
845    },
846}
847
848/// Marks a replica as a profiling capture target and carries its window
849/// control ([[RFC-0004:C-WORKLOAD-PROFILING]]).
850#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
851#[serde(deny_unknown_fields)]
852pub struct CaptureTargetRequirement {
853    pub window_control: CaptureWindowControlRequirement,
854}
855
856/// The logical workload endpoint and typed actions that open and close a
857/// capture window.
858#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
859#[serde(deny_unknown_fields)]
860pub struct CaptureWindowControlRequirement {
861    pub endpoint: CaptureWindowControlEndpoint,
862    pub start: HttpActionSpec,
863    pub stop: HttpActionSpec,
864}
865
866/// The logical workload endpoint exposing a capture target's window-control
867/// actions.
868#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
869#[serde(rename_all = "snake_case")]
870pub enum CaptureWindowControlEndpoint {
871    /// The entry process of the capture target's Engine replica.
872    ReplicaEntry,
873    /// The separately planned Gateway process.
874    Gateway,
875}
876
877/// The final process invocations returned by a `RenderServe`, one per supplied
878/// allocation.
879#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
880#[serde(deny_unknown_fields)]
881pub struct RenderServeResult {
882    pub integration: IntegrationIdentity,
883    pub processes: Vec<RenderedServeProcess>,
884}
885
886/// A rendered process bound to the model-rank or frontend allocation identity
887/// it was produced for.
888#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
889#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
890pub enum RenderedServeProcess {
891    ModelRank {
892        process: String,
893        role: String,
894        replica: u32,
895        rank: u32,
896        rank_count: u32,
897        launch_files: Vec<LaunchFileDeclaration>,
898        command: ProcessSpec,
899    },
900    Frontend {
901        process: String,
902        process_role: FrontendProcessRole,
903        components: FrontendComponents,
904        launch_files: Vec<LaunchFileDeclaration>,
905        command: ProcessSpec,
906    },
907}
908
909/// One immutable text input a rendered process requires before it can launch.
910#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
911#[serde(deny_unknown_fields)]
912pub struct LaunchFileDeclaration {
913    pub relative_path: String,
914    pub text: String,
915    pub sha256: String,
916}
917
918/// An HTTP action (method and path) invoked against the workload endpoint.
919#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
920#[serde(deny_unknown_fields)]
921pub struct HttpActionSpec {
922    pub method: HttpMethod,
923    pub path: String,
924}
925
926/// The HTTP method of an [`HttpActionSpec`].
927#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
928#[serde(rename_all = "snake_case")]
929pub enum HttpMethod {
930    /// HTTP POST.
931    Post,
932}
933
934/// The integration's identity recorded on its results: adapter id, adapter
935/// version, and the framework it lowers to.
936#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
937#[serde(deny_unknown_fields)]
938pub struct IntegrationIdentity {
939    pub adapter_id: String,
940    pub adapter_version: String,
941    pub framework: String,
942    pub framework_version: String,
943}
944
945/// A launchable process: its argument vector and environment.
946#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
947#[serde(deny_unknown_fields)]
948pub struct ProcessSpec {
949    pub argv: Vec<String>,
950    pub env: BTreeMap<String, String>,
951}
952
953/// How the control plane decides a process is ready.
954#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
955#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
956pub enum ReadinessProbe {
957    /// Ready when an HTTP GET of `path` succeeds.
958    Http { path: String },
959    /// Ready when the public endpoint succeeds and its HTTP target registry
960    /// contains every control-plane-derived serving target.
961    HttpTargetRegistry(Box<HttpTargetRegistryReadiness>),
962    /// Ready as soon as the process is alive.
963    ProcessAlive,
964}
965
966/// The integration-owned HTTP registry contract for target-aware readiness.
967#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
968#[serde(deny_unknown_fields)]
969pub struct HttpTargetRegistryReadiness {
970    pub target_scheme: TargetEndpointScheme,
971    pub readiness_path: String,
972    pub registry_path: String,
973    pub targets_field: String,
974    pub target_url_field: String,
975    pub target_role_field: String,
976    pub target_healthy_field: String,
977    pub target_bootstrap_port_field: String,
978    pub prefill_role_value: String,
979    pub decode_role_value: String,
980    pub prefill_bootstrap_port: String,
981}
982
983/// The application protocol used to identify serving targets in a registry.
984#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
985#[serde(rename_all = "snake_case")]
986pub enum TargetEndpointScheme {
987    /// HTTP serving endpoint.
988    Http,
989    /// gRPC serving endpoint.
990    Grpc,
991}
992
993/// The workload endpoint's protocol and named OpenAI paths, plus an optional
994/// prefix-cache-reset action a Bench case can invoke between runs.
995#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
996#[serde(deny_unknown_fields)]
997pub struct EndpointRequirement {
998    pub protocol: EndpointProtocol,
999    pub completions_path: String,
1000    pub chat_completions_path: String,
1001    #[serde(default, skip_serializing_if = "Option::is_none")]
1002    pub prefix_cache_reset: Option<HttpActionSpec>,
1003}
1004
1005/// The application protocol a workload endpoint speaks.
1006#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1007#[serde(rename_all = "snake_case")]
1008pub enum EndpointProtocol {
1009    /// HTTP.
1010    Http,
1011}
1012
1013/// A structured rejection an integration returns in an [`AdapterResponse::Error`].
1014#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1015#[serde(deny_unknown_fields)]
1016pub struct AdapterError {
1017    pub code: AdapterErrorCode,
1018    pub message: String,
1019}
1020
1021/// Machine-readable failure category an adapter reports in an [`AdapterError`].
1022#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1023#[serde(rename_all = "snake_case")]
1024pub enum AdapterErrorCode {
1025    /// The request was malformed or missing required fields.
1026    InvalidRequest,
1027    /// The request's protocol version is not accepted.
1028    UnsupportedProtocolVersion,
1029    /// A framework setting was unknown or invalid.
1030    InvalidSettings,
1031    /// An unexpected internal failure occurred in the integration.
1032    Internal,
1033    /// The requested operation is not supported by this integration.
1034    UnsupportedOperation,
1035}
1036
1037/// The request the Eval measurement runtime passes to its client: the endpoint
1038/// to hit, the model, the eval definition, and where to write artifacts.
1039#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1040#[serde(deny_unknown_fields)]
1041pub struct EvalClientRequest {
1042    pub protocol_version: ProtocolVersion,
1043    pub workspace_root: PathBuf,
1044    pub workspace_source_exclusions: Vec<PathBuf>,
1045    pub endpoint: ClientEndpointInput,
1046    pub model: MeasurementModelInput,
1047    pub definition: EvalDefinitionInput,
1048    /// Remaining control-plane case budget when the client is released.
1049    pub case_budget_seconds: f64,
1050    pub artifact_dir: PathBuf,
1051}
1052
1053/// The request the Bench measurement runtime passes to its client: the
1054/// endpoint, model, bench definition, the load case to run, and the artifact
1055/// directory.
1056#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1057#[serde(deny_unknown_fields)]
1058pub struct BenchClientRequest {
1059    pub protocol_version: ProtocolVersion,
1060    pub endpoint: ClientEndpointInput,
1061    pub model: MeasurementModelInput,
1062    pub definition: BenchDefinitionInput,
1063    #[serde(default)]
1064    pub population: Option<BenchPopulationInput>,
1065    pub case: BenchCaseInput,
1066    /// Remaining control-plane case budget when the client is released.
1067    pub case_budget_seconds: f64,
1068    pub artifact_dir: PathBuf,
1069}
1070
1071/// A single Bench case: its load shape and the number of requests to send.
1072#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1073#[serde(deny_unknown_fields)]
1074pub struct BenchCaseInput {
1075    pub load_shape: BenchLoadInput,
1076    pub request_count: u32,
1077    #[serde(default)]
1078    pub warmup_request_count: u32,
1079}
1080
1081/// How a Bench case paces its requests.
1082#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1083#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1084pub enum BenchLoadInput {
1085    /// A fixed number of in-flight requests.
1086    ConcurrencyLimited { concurrency: u32 },
1087    /// A target arrival rate, optionally shaped by a burstiness factor.
1088    RequestRateLimited {
1089        request_rate: f64,
1090        burstiness: Option<f64>,
1091    },
1092    /// All requests issued as fast as possible.
1093    UnboundedRequestRate,
1094}
1095
1096/// The terminal outcome a measurement client reports.
1097#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1098#[serde(rename_all = "snake_case")]
1099pub enum ClientStatus {
1100    /// The client completed its measurement successfully.
1101    Succeeded,
1102    /// The client did not complete successfully.
1103    Failed,
1104}
1105
1106/// A typed Eval failure category preserved across the client boundary.
1107#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1108#[serde(rename_all = "snake_case")]
1109pub enum EvalFailureKind {
1110    TaskResolution,
1111    ProbeTokenizer,
1112    ProbeTransport,
1113    ProbeHttp,
1114    ProbeMalformedResponse,
1115    ProbeGeneratedOnlyLogprobs,
1116    ProbeTokenizerAlignment,
1117    MetricNormalization,
1118}
1119
1120/// The threshold comparison selected from lm-eval's scoring direction.
1121#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1122#[serde(rename_all = "snake_case")]
1123pub enum EvalMetricComparison {
1124    AtLeast,
1125    AtMost,
1126}
1127
1128/// The terminal conclusion of an lm-eval metric threshold gate.
1129#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1130#[serde(rename_all = "snake_case")]
1131pub enum EvalMetricGateConclusion {
1132    Passed,
1133    Failed,
1134}
1135
1136/// One finite lm-eval metric with its exact native provenance.
1137#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1138#[serde(deny_unknown_fields)]
1139pub struct EvalNormalizedMetric {
1140    pub source_identity: String,
1141    pub metric: String,
1142    pub filter: Option<String>,
1143    pub native_metric_key: String,
1144    pub value: f64,
1145    pub higher_is_better: bool,
1146}
1147
1148/// The effective threshold comparison for the configured primary metric.
1149#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1150#[serde(deny_unknown_fields)]
1151pub struct EvalMetricGate {
1152    pub metric: EvalNormalizedMetric,
1153    pub threshold: f64,
1154    pub comparison: EvalMetricComparison,
1155    pub conclusion: EvalMetricGateConclusion,
1156}
1157
1158/// Reconstructible aggregate counts for fixed outer Eval trials.
1159#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1160#[serde(deny_unknown_fields)]
1161pub struct EvalTrialSummary {
1162    pub requested_trials: u32,
1163    pub issued_trials: u32,
1164    pub unissued_trials: u32,
1165    pub completed_trials: u32,
1166    pub request_failure_trials: u32,
1167    pub passed_trials: u32,
1168    pub pass_rate: Option<f64>,
1169    pub per_trial_metric: String,
1170    pub per_trial_filter: Option<String>,
1171    pub higher_is_better: bool,
1172}
1173
1174/// The result an Eval client writes for the measurement runtime to consume.
1175#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1176#[serde(deny_unknown_fields)]
1177pub struct EvalClientResult {
1178    /// Result envelope version; clients write `1`. The measurement runtime
1179    /// rejects an eval result whose version is not `1`.
1180    pub schema_version: u32,
1181    pub status: ClientStatus,
1182    pub metrics: BTreeMap<String, f64>,
1183    #[serde(default)]
1184    pub normalized_metrics: BTreeMap<String, EvalNormalizedMetric>,
1185    #[serde(default)]
1186    pub gate: Option<EvalMetricGate>,
1187    #[serde(default)]
1188    pub trial_summary: Option<EvalTrialSummary>,
1189    pub native_command: Vec<String>,
1190    #[serde(default)]
1191    pub native_exit_code: Option<i32>,
1192    #[serde(default)]
1193    pub native_timed_out: bool,
1194    pub raw_artifacts: Vec<RawArtifact>,
1195    pub failure_kind: Option<EvalFailureKind>,
1196    pub error: Option<String>,
1197}
1198
1199/// The result a Bench client writes for the measurement runtime to consume.
1200#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1201#[serde(deny_unknown_fields)]
1202pub struct BenchClientResult {
1203    /// Result envelope version; clients write `1`. The measurement runtime
1204    /// rejects a bench result whose version is not `1`.
1205    pub schema_version: u32,
1206    pub status: ClientStatus,
1207    pub completed_requests: u64,
1208    pub failed_requests: u64,
1209    pub normalization_schema: String,
1210    pub metrics: BTreeMap<String, f64>,
1211    #[serde(default)]
1212    pub request_slo: Option<BenchRequestSloResult>,
1213    pub native_command: Vec<String>,
1214    pub native_exit_code: Option<i32>,
1215    pub raw_artifacts: Vec<RawArtifact>,
1216    pub error: Option<String>,
1217}
1218
1219/// File-bound request-SLO evidence derived from AIPerf profiling records.
1220#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1221#[serde(deny_unknown_fields)]
1222pub struct BenchRequestSloResult {
1223    pub good_requests: u64,
1224    pub good_request_ratio: f64,
1225    pub goodput: f64,
1226    pub profiling_duration_seconds: f64,
1227    pub profiling_duration_source: String,
1228    pub request_count_reconciled: bool,
1229    #[serde(default)]
1230    pub native_aggregate_good_request_count: Option<u64>,
1231    #[serde(default)]
1232    pub native_aggregate_good_request_count_consistent: Option<bool>,
1233}
1234
1235/// A raw output file a client produced, retained as workload evidence.
1236#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1237#[serde(deny_unknown_fields)]
1238pub struct RawArtifact {
1239    pub name: String,
1240    pub kind: String,
1241    pub path: PathBuf,
1242}
1243
1244/// The schema root aggregating every wire type. It exists to generate one
1245/// committed JSON schema (and the Python SDK models); its optional client
1246/// fields are never all populated in a single message.
1247#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1248#[serde(deny_unknown_fields)]
1249pub struct AdapterProtocol {
1250    pub request: AdapterRequest,
1251    pub response: AdapterResponse,
1252    #[serde(default, skip_serializing_if = "Option::is_none")]
1253    pub eval_client_request: Option<EvalClientRequest>,
1254    #[serde(default, skip_serializing_if = "Option::is_none")]
1255    pub eval_client_result: Option<EvalClientResult>,
1256    #[serde(default, skip_serializing_if = "Option::is_none")]
1257    pub bench_client_request: Option<BenchClientRequest>,
1258    #[serde(default, skip_serializing_if = "Option::is_none")]
1259    pub bench_client_result: Option<BenchClientResult>,
1260    #[serde(default, skip_serializing_if = "Option::is_none")]
1261    pub bench_dataset_preparation_request: Option<BenchDatasetPreparationRequest>,
1262    #[serde(default, skip_serializing_if = "Option::is_none")]
1263    pub bench_dataset_preparation_result: Option<BenchDatasetPreparationResult>,
1264}