Skip to main content

inferlab_protocol/
wire.rs

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