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    #[serde(default)]
311    pub server_metrics: Option<ServerMetricsEndpointInput>,
312}
313
314/// The resolved server-metrics endpoint supplied to a measurement client.
315#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
316#[serde(deny_unknown_fields)]
317pub struct ServerMetricsEndpointInput {
318    pub path: String,
319    #[serde(default)]
320    pub port_name: Option<String>,
321    pub url: String,
322}
323
324/// The measurement an Eval client runs against the workload endpoint.
325#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
326#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
327pub enum EvalDefinitionInput {
328    /// A single-prompt liveness check.
329    #[serde(rename = "openai_smoke")]
330    OpenAiSmoke {
331        prompt: String,
332        max_tokens: u32,
333        timeout_seconds: u64,
334    },
335    /// An lm-eval task run with a pass threshold on the chosen metric.
336    LmEval {
337        task: Box<EvalTaskSourceInput>,
338        #[serde(default)]
339        request_body: BTreeMap<String, SettingValue>,
340        limit: Option<u32>,
341        few_shot: Option<u32>,
342        seed: Option<u64>,
343        trials: u32,
344        max_tokens: Option<u32>,
345        concurrency: Option<u32>,
346        metric: String,
347        #[serde(default)]
348        metric_filter: Option<String>,
349        threshold: f64,
350        timeout_seconds: u64,
351    },
352}
353
354/// The resolved lm-eval task source consumed by the release-owned Eval runner.
355#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
356#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
357pub enum EvalTaskSourceInput {
358    /// One individual task shipped by the pinned lm-eval runtime.
359    BuiltIn { name: String },
360    /// One task closure shipped and identity-bound by the Inferlab release.
361    Bundled {
362        name: String,
363        task_identity: String,
364        path: PathBuf,
365        task_closure_sha256: String,
366        task_definition_sha256: String,
367        prompt_asset_sha256: String,
368        dataset_asset_sha256: String,
369        scorer_sha256: String,
370    },
371    /// One validated workspace-local lm-eval YAML file.
372    WorkspaceYaml { path: PathBuf },
373}
374
375/// The workload shape a Bench client drives, shared across its load cases.
376#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
377#[serde(deny_unknown_fields)]
378pub struct BenchDefinitionInput {
379    #[serde(default)]
380    pub request_source: Option<BenchRequestSourceInput>,
381    #[serde(default)]
382    pub session_source: Option<BenchSessionSourceInput>,
383    #[serde(default)]
384    pub server_metrics: bool,
385    pub seed: u64,
386    #[serde(default)]
387    pub request_body: BTreeMap<String, SettingValue>,
388    #[serde(default)]
389    pub request_slo: Option<BenchRequestSloInput>,
390    pub timeout_seconds: u64,
391    #[serde(default)]
392    pub reset_prefix_cache: bool,
393}
394
395/// One closed request origin lowered by Inferlab for the Bench runtime.
396#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
397#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
398pub enum BenchRequestSourceInput {
399    /// AIPerf generates exact token-shape prompts from the release-pinned
400    /// synthetic generator.
401    Random {
402        input_tokens: BenchTokenSelectorInput,
403        output_tokens: BenchTokenSelectorInput,
404        #[serde(default)]
405        prefix_sharing: Option<BenchPrefixSharingInput>,
406    },
407    /// AIPerf samples exact token-shape pairs from one seeded categorical
408    /// distribution.
409    RandomMixture {
410        shapes: Vec<BenchRandomShapeInput>,
411        total_weight: u64,
412    },
413    /// Inferlab materializes a release-catalog conversation snapshot before
414    /// AIPerf starts.
415    Dataset {
416        dataset: String,
417        #[serde(default)]
418        profile: Option<String>,
419        max_input_tokens: u32,
420        output_tokens: Option<u32>,
421        catalog: Box<BenchDatasetCatalogInput>,
422    },
423}
424
425/// One release-qualified population of dependent linear session templates.
426#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
427#[serde(deny_unknown_fields)]
428pub struct BenchSessionSourceInput {
429    pub dataset: String,
430    #[serde(default)]
431    pub profile: Option<String>,
432    pub max_input_tokens: u32,
433    pub output_tokens: Option<u32>,
434    pub inter_turn_delay_scale: f64,
435    pub max_inter_turn_delay_seconds: Option<f64>,
436    pub catalog: Box<BenchSessionDatasetCatalogInput>,
437}
438
439/// One fixed or bounded inclusive-uniform token-length selector.
440#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
441#[serde(untagged)]
442pub enum BenchTokenSelectorInput {
443    Fixed(u32),
444    InclusiveUniform(BenchInclusiveUniformInput),
445}
446
447/// The one bounded distribution currently supported for a token selector.
448#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
449#[serde(deny_unknown_fields)]
450pub struct BenchInclusiveUniformInput {
451    pub kind: BenchTokenDistributionKindInput,
452    pub min: u32,
453    pub max: u32,
454}
455
456#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
457#[serde(rename_all = "snake_case")]
458pub enum BenchTokenDistributionKindInput {
459    InclusiveUniform,
460}
461
462/// The effective split between one shared system-message prefix and each
463/// request's independently generated user suffix.
464#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
465#[serde(deny_unknown_fields)]
466pub struct BenchPrefixSharingInput {
467    pub shared_prefix_ratio: f64,
468    pub shared_prefix_tokens: u32,
469    pub unique_suffix_tokens: u32,
470}
471
472/// One exact ISL/OSL pair and its relative categorical sampling weight.
473#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
474#[serde(deny_unknown_fields)]
475pub struct BenchRandomShapeInput {
476    pub input_tokens: u32,
477    pub output_tokens: u32,
478    pub weight: u32,
479}
480
481/// Immutable release-catalog facts resolved by the Rust control plane.
482#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
483#[serde(deny_unknown_fields)]
484pub struct BenchDatasetCatalogInput {
485    pub dataset: String,
486    #[serde(default)]
487    pub profile: Option<String>,
488    pub source: String,
489    pub upstream_identity: String,
490    pub url: String,
491    pub sha256: String,
492    pub source_format: String,
493    pub aiperf_format: String,
494    #[serde(default)]
495    pub configuration: Option<String>,
496    #[serde(default)]
497    pub split: Option<String>,
498    #[serde(default)]
499    pub filter: Option<BenchDatasetFilterInput>,
500    pub license: String,
501    pub cache_path: PathBuf,
502    pub cache_state: BenchDatasetCacheState,
503    pub materialization_identity: String,
504    pub provides_output_targets: bool,
505}
506
507/// Immutable release-catalog facts for a linear-session materializer. AIPerf
508/// loader names are deliberately absent from this source boundary.
509#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
510#[serde(deny_unknown_fields)]
511pub struct BenchSessionDatasetCatalogInput {
512    pub dataset: String,
513    #[serde(default)]
514    pub profile: Option<String>,
515    pub source: String,
516    pub upstream_identity: String,
517    pub url: String,
518    pub sha256: String,
519    pub source_format: String,
520    #[serde(default)]
521    pub configuration: Option<String>,
522    #[serde(default)]
523    pub split: Option<String>,
524    #[serde(default)]
525    pub filter: Option<BenchDatasetFilterInput>,
526    pub license: String,
527    pub cache_path: PathBuf,
528    pub cache_state: BenchDatasetCacheState,
529    pub materialization_identity: String,
530    pub provides_output_targets: bool,
531}
532
533#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
534#[serde(deny_unknown_fields)]
535pub struct BenchDatasetFilterInput {
536    pub field: String,
537    pub value: String,
538}
539
540/// Read-only cache state observed while resolving the Bench plan.
541#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
542#[serde(rename_all = "snake_case")]
543pub enum BenchDatasetCacheState {
544    Missing,
545    Present,
546}
547
548/// One frozen dataset population consumed sequentially by every Bench case.
549#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
550#[serde(deny_unknown_fields)]
551pub struct BenchPopulationInput {
552    pub path: PathBuf,
553    pub sha256: String,
554    pub entries: u32,
555    pub tpot_applicable: bool,
556    #[serde(default)]
557    pub session_templates: Vec<BenchSessionTemplateInput>,
558}
559
560/// One frozen linear-template summary needed to assign case slices without
561/// duplicating the content-bearing population evidence artifact.
562#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
563#[serde(deny_unknown_fields)]
564pub struct BenchSessionTemplateInput {
565    pub template_identity: String,
566    pub turn_count: u32,
567}
568
569/// The bounded tokenizer-backed operation that freezes one request population
570/// before any Bench case starts.
571#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
572#[serde(deny_unknown_fields)]
573pub struct BenchPopulationPreparationRequest {
574    pub protocol_version: ProtocolVersion,
575    pub model: MeasurementModelInput,
576    pub tokenizer_backend: String,
577    pub transformers_version: String,
578    #[serde(default)]
579    pub request_source: Option<BenchRequestSourceInput>,
580    #[serde(default)]
581    pub session_source: Option<BenchSessionSourceInput>,
582    #[serde(default)]
583    pub source_path: Option<PathBuf>,
584    pub required_entries: u32,
585    pub seed: u64,
586    #[serde(default)]
587    pub request_body: BTreeMap<String, SettingValue>,
588    pub artifact_dir: PathBuf,
589}
590
591/// Summary of the realized token counts. Exact per-entry values remain in the
592/// population evidence artifact.
593#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
594#[serde(deny_unknown_fields)]
595pub struct BenchTokenCountSummary {
596    pub minimum: u32,
597    pub maximum: u32,
598    pub mean: f64,
599}
600
601/// Where the concrete template used for local prompt-length projection came
602/// from. The model server remains authoritative for transport rendering.
603#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
604#[serde(rename_all = "snake_case")]
605pub enum BenchPromptTemplateSource {
606    RequestBody,
607    TokenizerDefault,
608}
609
610/// The concrete template used only for local complete-prompt projection.
611#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
612#[serde(deny_unknown_fields)]
613pub struct BenchPromptTemplateProjection {
614    pub source: BenchPromptTemplateSource,
615    pub content: String,
616    pub sha256: String,
617}
618
619/// Summary of best-effort complete-prompt targeting for a synthetic population.
620#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
621#[serde(deny_unknown_fields)]
622pub struct BenchPromptTokenTargetingSummary {
623    pub selected_prompt_tokens: BenchTokenCountSummary,
624    pub pre_template_content_tokens: BenchTokenCountSummary,
625    #[serde(default)]
626    pub projection_template: Option<BenchPromptTemplateProjection>,
627    pub exact_entries: u32,
628    pub fallback_entries: u32,
629    #[serde(default)]
630    pub fallback_reasons: BTreeMap<String, u32>,
631}
632
633/// Terminal result of request-population materialization.
634#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
635#[serde(deny_unknown_fields)]
636pub struct BenchPopulationPreparationResult {
637    pub schema_version: u32,
638    pub status: ClientStatus,
639    pub materialization_identity: String,
640    pub requested_entries: u32,
641    pub candidate_entries: u64,
642    pub admitted_entries: u64,
643    pub ineligible_entries: u64,
644    #[serde(default)]
645    pub ineligible_reasons: BTreeMap<String, u64>,
646    pub population: Option<BenchPopulationInput>,
647    pub input_tokens: Option<BenchTokenCountSummary>,
648    pub output_tokens: Option<BenchTokenCountSummary>,
649    #[serde(default)]
650    pub prompt_token_targeting: Option<BenchPromptTokenTargetingSummary>,
651    pub evidence_path: Option<PathBuf>,
652    pub error: Option<String>,
653}
654
655/// Per-request latency bounds lowered to the release-owned Bench runner.
656#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
657#[serde(deny_unknown_fields)]
658pub struct BenchRequestSloInput {
659    #[serde(default)]
660    pub request_latency_ms: Option<f64>,
661    #[serde(default)]
662    pub ttft_ms: Option<f64>,
663    #[serde(default)]
664    pub tpot_ms: Option<f64>,
665    pub minimum_good_request_ratio: f64,
666}
667
668/// A framework-specific server setting value carried as structured JSON data
669/// (never a pre-rendered shell fragment) across the integration boundary.
670#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
671#[serde(untagged)]
672pub enum SettingValue {
673    /// A JSON boolean.
674    Bool(bool),
675    /// A JSON integer.
676    Integer(i64),
677    /// A JSON floating-point number.
678    Float(f64),
679    /// A JSON string.
680    String(String),
681    /// A JSON array of setting values.
682    Array(Vec<SettingValue>),
683    /// A JSON object of named setting values.
684    Object(BTreeMap<String, SettingValue>),
685}
686
687/// The one JSON response an integration writes to stdout, tagged by outcome.
688#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
689#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
690pub enum AdapterResponse {
691    /// The operation succeeded and carries its result.
692    Ok {
693        protocol_version: ProtocolVersion,
694        result: Box<AdapterResult>,
695    },
696    /// The operation was rejected with a structured error.
697    Error {
698        protocol_version: ProtocolVersion,
699        error: AdapterError,
700    },
701}
702
703impl AdapterResponse {
704    /// The protocol version carried by this response, regardless of outcome.
705    #[must_use]
706    pub const fn protocol_version(&self) -> ProtocolVersion {
707        match self {
708            Self::Ok {
709                protocol_version, ..
710            }
711            | Self::Error {
712                protocol_version, ..
713            } => *protocol_version,
714        }
715    }
716}
717
718/// The successful result of an [`AdapterRequest`], tagged by the operation it
719/// answers.
720#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
721#[serde(tag = "operation", rename_all = "snake_case", deny_unknown_fields)]
722pub enum AdapterResult {
723    /// The planned topology from a `PlanServe` request.
724    PlanServe { output: Box<PlanServeResult> },
725    /// The rendered process invocations from a `RenderServe` request.
726    RenderServe { output: Box<RenderServeResult> },
727}
728
729/// The lowered topology returned by a `PlanServe`: effective Engine roles,
730/// whole-replica requirements, logical links, and separate optional Gateway
731/// and P/D Router component plans.
732#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
733#[serde(deny_unknown_fields)]
734pub struct PlanServeResult {
735    pub integration: IntegrationIdentity,
736    pub roles: Vec<ServeRoleResult>,
737    pub replicas: Vec<ServeReplicaRequirement>,
738    pub links: Vec<ServeRoleLink>,
739    #[serde(default, skip_serializing_if = "Option::is_none")]
740    pub gateway: Option<GatewayPlan>,
741    #[serde(default, skip_serializing_if = "Option::is_none")]
742    pub pd_router: Option<PdRouterPlan>,
743}
744
745/// Which bounded implementation renders an accepted concrete frontend
746/// allocation. This is a lowering boundary only; the control plane always
747/// owns placement, lifecycle, cleanup, endpoints, and records.
748#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
749#[serde(rename_all = "snake_case")]
750pub enum RenderSource {
751    ControlPlane,
752    Integration,
753}
754
755/// The one canonical process role available to a protocol-v7 frontend.
756#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
757#[serde(rename_all = "snake_case")]
758pub enum FrontendProcessRole {
759    Gateway,
760}
761
762/// The fixed co-rendering requirement shared by compatible frontend plans.
763#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
764#[serde(deny_unknown_fields)]
765pub struct FrontendCoRendering {
766    pub process_role: FrontendProcessRole,
767}
768
769/// The literal first member of every closed frontend component binding.
770#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
771#[serde(rename_all = "snake_case")]
772pub enum FrontendGatewayComponent {
773    Gateway,
774}
775
776/// The literal second member of the fused P/D frontend component binding.
777#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
778#[serde(rename_all = "snake_case")]
779pub enum FrontendPdRouterComponent {
780    PdRouter,
781}
782
783/// The stable schema branch for a Gateway-only frontend binding.
784#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
785#[serde(transparent)]
786pub struct GatewayFrontendBinding(pub [FrontendGatewayComponent; 1]);
787
788/// The stable schema branch for a fused Gateway and P/D Router binding.
789#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
790#[serde(transparent)]
791pub struct GatewayPdRouterFrontendBinding(
792    pub (FrontendGatewayComponent, FrontendPdRouterComponent),
793);
794
795/// The only two frontend bindings protocol v7 accepts. Tuple representation
796/// deliberately serializes as the closed ordered arrays `["gateway"]` and
797/// `["gateway", "pd_router"]` rather than as an open component list.
798#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
799#[serde(untagged)]
800pub enum FrontendComponents {
801    Gateway(GatewayFrontendBinding),
802    GatewayPdRouter(GatewayPdRouterFrontendBinding),
803}
804
805impl FrontendComponents {
806    #[must_use]
807    pub const fn gateway() -> Self {
808        Self::Gateway(GatewayFrontendBinding([FrontendGatewayComponent::Gateway]))
809    }
810
811    #[must_use]
812    pub const fn gateway_pd_router() -> Self {
813        Self::GatewayPdRouter(GatewayPdRouterFrontendBinding((
814            FrontendGatewayComponent::Gateway,
815            FrontendPdRouterComponent::PdRouter,
816        )))
817    }
818
819    #[must_use]
820    pub const fn includes_pd_router(&self) -> bool {
821        matches!(self, Self::GatewayPdRouter(_))
822    }
823}
824
825/// The target a Gateway forwards accepted public requests to.
826#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
827#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
828pub enum GatewayTarget {
829    /// A routed-single Gateway targets the sole Engine role entry point.
830    Engine { role: String },
831    /// A P/D Gateway hands requests to its co-rendered P/D Router component.
832    PdRouter,
833}
834
835/// The public frontend component plan returned independently from Engine and
836/// P/D Router planning.
837#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
838#[serde(deny_unknown_fields)]
839pub struct GatewayPlan {
840    pub backend: String,
841    pub implementation: String,
842    pub implementation_version: String,
843    pub effective_settings: BTreeMap<String, SettingValue>,
844    pub endpoint: EndpointRequirement,
845    pub readiness: ReadinessProbe,
846    #[serde(default)]
847    pub ports: Vec<String>,
848    pub targets: Vec<GatewayTarget>,
849    #[serde(default)]
850    pub render_inputs: Vec<RenderInputDeclaration>,
851    pub render_source: RenderSource,
852    pub co_rendering: FrontendCoRendering,
853}
854
855/// Independent policies for choosing prefill and decode targets.
856#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
857#[serde(deny_unknown_fields)]
858pub struct PdRoutingPolicies {
859    pub prefill: String,
860    pub decode: String,
861}
862
863/// The currently demonstrated Gateway-to-P/D Router handoff is internal to
864/// one fused frontend process.
865#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
866#[serde(rename_all = "snake_case")]
867pub enum FrontendHandoff {
868    InProcess,
869}
870
871/// The P/D orchestration component plan, kept separate even when the same
872/// process and provider also realize Gateway.
873#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
874#[serde(deny_unknown_fields)]
875pub struct PdRouterPlan {
876    pub backend: String,
877    pub implementation: String,
878    pub implementation_version: String,
879    pub effective_settings: BTreeMap<String, SettingValue>,
880    pub policies: PdRoutingPolicies,
881    pub prefill_role: String,
882    pub decode_role: String,
883    pub target_scheme: TargetEndpointScheme,
884    #[serde(default)]
885    pub ports: Vec<String>,
886    pub readiness: ReadinessProbe,
887    pub handoff: FrontendHandoff,
888    #[serde(default)]
889    pub render_inputs: Vec<RenderInputDeclaration>,
890    pub render_source: RenderSource,
891    pub co_rendering: FrontendCoRendering,
892}
893
894/// One workspace-authored UTF-8 source file an integration declares during
895/// planning for the control plane to supply during final rendering.
896#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
897#[serde(deny_unknown_fields)]
898pub struct RenderInputDeclaration {
899    pub source_path: String,
900}
901
902/// The original declared path plus the exact UTF-8 contents and digest the
903/// control plane supplies to an integration during final rendering.
904#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
905#[serde(deny_unknown_fields)]
906pub struct SuppliedRenderInput {
907    pub source_path: String,
908    pub text: String,
909    pub sha256: String,
910}
911
912/// A whole-replica resource and readiness requirement the integration declares
913/// without choosing placement, ranks, or concrete endpoints.
914#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
915#[serde(deny_unknown_fields)]
916pub struct ServeReplicaRequirement {
917    pub id: String,
918    pub role_id: String,
919    pub replica_index: u32,
920    pub device_count: u32,
921    pub ports: Vec<String>,
922    pub primary_ports: Vec<String>,
923    pub primary_readiness: ReadinessProbe,
924    pub worker_readiness: ReadinessProbe,
925    #[serde(default, skip_serializing_if = "Option::is_none")]
926    pub capture_target: Option<CaptureTargetRequirement>,
927}
928
929/// One concrete process allocation supplied to `RenderServe`. Model-rank and
930/// process-only frontend identities are distinct so a frontend cannot acquire
931/// model coordinates or a model locator by construction.
932#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
933#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
934pub enum ServeProcessAllocation {
935    ModelRank {
936        process: String,
937        role: String,
938        role_kind: ServeRoleKind,
939        replica: u32,
940        rank: u32,
941        rank_count: u32,
942        machine: String,
943        devices: Vec<u32>,
944        model_locator: String,
945        #[serde(default, skip_serializing_if = "Option::is_none")]
946        endpoint: Option<EndpointAssignment>,
947        ports: BTreeMap<String, EndpointAssignment>,
948        cache: String,
949        launch: AllocationLaunch,
950        effective_settings: BTreeMap<String, SettingValue>,
951        effective_parallelism: Parallelism,
952        #[serde(default)]
953        links: Vec<ServeRoleLink>,
954        #[serde(default)]
955        dependencies: Vec<String>,
956        #[serde(default)]
957        render_inputs: Vec<SuppliedRenderInput>,
958    },
959    Frontend {
960        process: String,
961        process_role: FrontendProcessRole,
962        components: FrontendComponents,
963        machine: String,
964        devices: Vec<u32>,
965        endpoint: EndpointAssignment,
966        ports: BTreeMap<String, EndpointAssignment>,
967        cache: String,
968        launch: AllocationLaunch,
969        gateway: Box<GatewayPlan>,
970        #[serde(default, skip_serializing_if = "Option::is_none")]
971        pd_router: Option<Box<PdRouterPlan>>,
972        #[serde(default)]
973        links: Vec<ServeRoleLink>,
974        #[serde(default)]
975        dependencies: Vec<String>,
976        #[serde(default)]
977        render_inputs: Vec<SuppliedRenderInput>,
978    },
979}
980
981/// The machine-local launch channel selected by the control plane.
982#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
983#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
984pub enum AllocationLaunch {
985    Local,
986    Ssh { target: String },
987}
988
989/// A directed link between serve roles the integration declares as part of the
990/// topology.
991#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
992#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
993pub enum ServeRoleLink {
994    /// The source role routes requests to the target roles.
995    RequestRouting {
996        source: String,
997        targets: Vec<String>,
998    },
999    /// KV cache is transferred from source to target over `mechanism`.
1000    KvTransfer {
1001        source: String,
1002        target: String,
1003        mechanism: KvTransferMechanism,
1004    },
1005    /// The source discovers the target through a bootstrap port.
1006    Bootstrap {
1007        source: String,
1008        target: String,
1009        port: String,
1010    },
1011    /// The source and target exchange out-of-band data over a side-channel port.
1012    SideChannel {
1013        source: String,
1014        target: String,
1015        port: String,
1016    },
1017}
1018
1019/// Marks a replica as a profiling capture target and carries its window
1020/// control ([[RFC-0004:C-WORKLOAD-PROFILING]]).
1021#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1022#[serde(deny_unknown_fields)]
1023pub struct CaptureTargetRequirement {
1024    pub window_control: CaptureWindowControlRequirement,
1025}
1026
1027/// The logical workload endpoint and typed actions that open and close a
1028/// capture window.
1029#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1030#[serde(deny_unknown_fields)]
1031pub struct CaptureWindowControlRequirement {
1032    pub endpoint: CaptureWindowControlEndpoint,
1033    pub start: CaptureWindowHttpActionSpec,
1034    pub stop: CaptureWindowHttpActionSpec,
1035}
1036
1037/// The logical workload endpoint exposing a capture target's window-control
1038/// actions.
1039#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1040#[serde(rename_all = "snake_case")]
1041pub enum CaptureWindowControlEndpoint {
1042    /// The entry process of the capture target's Engine replica.
1043    ReplicaEntry,
1044    /// The separately planned Gateway process.
1045    Gateway,
1046}
1047
1048/// The final process invocations returned by a `RenderServe`, one per supplied
1049/// allocation.
1050#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1051#[serde(deny_unknown_fields)]
1052pub struct RenderServeResult {
1053    pub integration: IntegrationIdentity,
1054    pub processes: Vec<RenderedServeProcess>,
1055}
1056
1057/// A rendered process bound to the model-rank or frontend allocation identity
1058/// it was produced for.
1059#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1060#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1061pub enum RenderedServeProcess {
1062    ModelRank {
1063        process: String,
1064        role: String,
1065        replica: u32,
1066        rank: u32,
1067        rank_count: u32,
1068        launch_files: Vec<LaunchFileDeclaration>,
1069        command: ProcessSpec,
1070    },
1071    Frontend {
1072        process: String,
1073        process_role: FrontendProcessRole,
1074        components: FrontendComponents,
1075        launch_files: Vec<LaunchFileDeclaration>,
1076        command: ProcessSpec,
1077    },
1078}
1079
1080/// One immutable text input a rendered process requires before it can launch.
1081#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1082#[serde(deny_unknown_fields)]
1083pub struct LaunchFileDeclaration {
1084    pub relative_path: String,
1085    pub text: String,
1086    pub sha256: String,
1087}
1088
1089/// A capture-window HTTP action invoked against a logical serving endpoint.
1090/// The integration owns any framework-specific JSON body; the control plane
1091/// owns execution and evidence.
1092#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1093#[serde(deny_unknown_fields)]
1094pub struct CaptureWindowHttpActionSpec {
1095    pub method: HttpMethod,
1096    pub path: String,
1097    #[serde(default, skip_serializing_if = "Option::is_none")]
1098    pub body: Option<BTreeMap<String, SettingValue>>,
1099}
1100
1101/// An HTTP action invoked against a logical serving endpoint.
1102#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1103#[serde(deny_unknown_fields)]
1104pub struct HttpActionSpec {
1105    pub method: HttpMethod,
1106    pub path: String,
1107}
1108
1109/// The HTTP method of an action specification.
1110#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1111#[serde(rename_all = "snake_case")]
1112pub enum HttpMethod {
1113    /// HTTP POST.
1114    Post,
1115}
1116
1117/// The integration's identity recorded on its results: adapter id, adapter
1118/// version, and the framework it lowers to.
1119#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1120#[serde(deny_unknown_fields)]
1121pub struct IntegrationIdentity {
1122    pub adapter_id: String,
1123    pub adapter_version: String,
1124    pub framework: String,
1125    pub framework_version: String,
1126}
1127
1128/// A launchable process: its argument vector and environment.
1129#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1130#[serde(deny_unknown_fields)]
1131pub struct ProcessSpec {
1132    pub argv: Vec<String>,
1133    pub env: BTreeMap<String, String>,
1134}
1135
1136/// How the control plane decides a process is ready.
1137#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1138#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1139pub enum ReadinessProbe {
1140    /// Ready when an HTTP GET of `path` succeeds.
1141    Http { path: String },
1142    /// Ready when the public endpoint succeeds and its HTTP target registry
1143    /// contains every control-plane-derived serving target.
1144    HttpTargetRegistry(Box<HttpTargetRegistryReadiness>),
1145    /// Ready as soon as the process is alive.
1146    ProcessAlive,
1147}
1148
1149/// The integration-owned HTTP registry contract for target-aware readiness.
1150#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1151#[serde(deny_unknown_fields)]
1152pub struct HttpTargetRegistryReadiness {
1153    pub target_scheme: TargetEndpointScheme,
1154    pub readiness_path: String,
1155    pub registry_path: String,
1156    pub targets_field: String,
1157    pub target_url_field: String,
1158    pub target_role_field: String,
1159    pub target_healthy_field: String,
1160    pub target_bootstrap_port_field: String,
1161    pub prefill_role_value: String,
1162    pub decode_role_value: String,
1163    pub prefill_bootstrap_port: String,
1164}
1165
1166/// The application protocol used to identify serving targets in a registry.
1167#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1168#[serde(rename_all = "snake_case")]
1169pub enum TargetEndpointScheme {
1170    /// HTTP serving endpoint.
1171    Http,
1172    /// gRPC serving endpoint.
1173    Grpc,
1174}
1175
1176/// The workload endpoint's protocol and named OpenAI paths, plus an optional
1177/// prefix-cache-reset action a Bench case can invoke between runs.
1178#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1179#[serde(deny_unknown_fields)]
1180pub struct EndpointRequirement {
1181    pub protocol: EndpointProtocol,
1182    pub completions_path: String,
1183    pub chat_completions_path: String,
1184    #[serde(default, skip_serializing_if = "Option::is_none")]
1185    pub server_metrics: Option<ServerMetricsEndpointRequirement>,
1186    #[serde(default, skip_serializing_if = "Option::is_none")]
1187    pub prefix_cache_reset: Option<HttpActionSpec>,
1188}
1189
1190/// An integration-owned logical server-metrics endpoint.
1191#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1192#[serde(deny_unknown_fields)]
1193pub struct ServerMetricsEndpointRequirement {
1194    pub path: String,
1195    #[serde(default, skip_serializing_if = "Option::is_none")]
1196    pub port: Option<String>,
1197}
1198
1199/// The application protocol a workload endpoint speaks.
1200#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1201#[serde(rename_all = "snake_case")]
1202pub enum EndpointProtocol {
1203    /// HTTP.
1204    Http,
1205}
1206
1207/// A structured rejection an integration returns in an [`AdapterResponse::Error`].
1208#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1209#[serde(deny_unknown_fields)]
1210pub struct AdapterError {
1211    pub code: AdapterErrorCode,
1212    pub message: String,
1213}
1214
1215/// Machine-readable failure category an adapter reports in an [`AdapterError`].
1216#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1217#[serde(rename_all = "snake_case")]
1218pub enum AdapterErrorCode {
1219    /// The request was malformed or missing required fields.
1220    InvalidRequest,
1221    /// The request's protocol version is not accepted.
1222    UnsupportedProtocolVersion,
1223    /// A framework setting was unknown or invalid.
1224    InvalidSettings,
1225    /// An unexpected internal failure occurred in the integration.
1226    Internal,
1227    /// The requested operation is not supported by this integration.
1228    UnsupportedOperation,
1229}
1230
1231/// The request the Eval measurement runtime passes to its client: the endpoint
1232/// to hit, the model, the eval definition, and where to write artifacts.
1233#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1234#[serde(deny_unknown_fields)]
1235pub struct EvalClientRequest {
1236    pub protocol_version: ProtocolVersion,
1237    pub workspace_root: PathBuf,
1238    pub workspace_source_exclusions: Vec<PathBuf>,
1239    pub endpoint: ClientEndpointInput,
1240    pub model: MeasurementModelInput,
1241    pub definition: EvalDefinitionInput,
1242    /// Remaining control-plane case budget when the client is released.
1243    pub case_budget_seconds: f64,
1244    pub artifact_dir: PathBuf,
1245}
1246
1247/// The request the Bench measurement runtime passes to its client: the
1248/// endpoint, model, bench definition, the load case to run, and the artifact
1249/// directory.
1250#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1251#[serde(deny_unknown_fields)]
1252pub struct BenchClientRequest {
1253    pub protocol_version: ProtocolVersion,
1254    pub endpoint: ClientEndpointInput,
1255    pub model: MeasurementModelInput,
1256    pub definition: BenchDefinitionInput,
1257    #[serde(default)]
1258    pub population: Option<BenchPopulationInput>,
1259    pub case: BenchCaseInput,
1260    /// Remaining control-plane case budget when the client is released.
1261    pub case_budget_seconds: f64,
1262    pub artifact_dir: PathBuf,
1263}
1264
1265/// A single Bench case: its load shape and the number of requests to send.
1266#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1267#[serde(deny_unknown_fields)]
1268pub struct BenchCaseInput {
1269    pub load_shape: BenchLoadInput,
1270    pub request_count: u32,
1271    #[serde(default)]
1272    pub warmup_request_count: u32,
1273    #[serde(default)]
1274    pub session_count: Option<u32>,
1275    #[serde(default)]
1276    pub warmup_session_count: Option<u32>,
1277}
1278
1279/// How a Bench case paces its requests.
1280#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1281#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1282pub enum BenchLoadInput {
1283    /// A fixed number of in-flight requests.
1284    ConcurrencyLimited { concurrency: u32 },
1285    /// A target arrival rate, optionally shaped by a burstiness factor.
1286    RequestRateLimited {
1287        request_rate: f64,
1288        burstiness: Option<f64>,
1289    },
1290    /// All requests issued as fast as possible.
1291    UnboundedRequestRate,
1292}
1293
1294/// The terminal outcome a measurement client reports.
1295#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1296#[serde(rename_all = "snake_case")]
1297pub enum ClientStatus {
1298    /// The client completed its measurement successfully.
1299    Succeeded,
1300    /// The client did not complete successfully.
1301    Failed,
1302}
1303
1304/// A typed Eval failure category preserved across the client boundary.
1305#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1306#[serde(rename_all = "snake_case")]
1307pub enum EvalFailureKind {
1308    TaskResolution,
1309    ProbeTokenizer,
1310    ProbeTransport,
1311    ProbeHttp,
1312    ProbeMalformedResponse,
1313    ProbeGeneratedOnlyLogprobs,
1314    ProbeTokenizerAlignment,
1315    MetricNormalization,
1316}
1317
1318/// The threshold comparison selected from lm-eval's scoring direction.
1319#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1320#[serde(rename_all = "snake_case")]
1321pub enum EvalMetricComparison {
1322    AtLeast,
1323    AtMost,
1324}
1325
1326/// The terminal conclusion of an lm-eval metric threshold gate.
1327#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1328#[serde(rename_all = "snake_case")]
1329pub enum EvalMetricGateConclusion {
1330    Passed,
1331    Failed,
1332}
1333
1334/// One finite lm-eval metric with its exact native provenance.
1335#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1336#[serde(deny_unknown_fields)]
1337pub struct EvalNormalizedMetric {
1338    pub source_identity: String,
1339    pub metric: String,
1340    pub filter: Option<String>,
1341    pub native_metric_key: String,
1342    pub value: f64,
1343    pub higher_is_better: bool,
1344}
1345
1346/// The effective threshold comparison for the configured primary metric.
1347#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1348#[serde(deny_unknown_fields)]
1349pub struct EvalMetricGate {
1350    pub metric: EvalNormalizedMetric,
1351    pub threshold: f64,
1352    pub comparison: EvalMetricComparison,
1353    pub conclusion: EvalMetricGateConclusion,
1354}
1355
1356/// Reconstructible aggregate counts for fixed outer Eval trials.
1357#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1358#[serde(deny_unknown_fields)]
1359pub struct EvalTrialSummary {
1360    pub requested_trials: u32,
1361    pub issued_trials: u32,
1362    pub unissued_trials: u32,
1363    pub completed_trials: u32,
1364    pub request_failure_trials: u32,
1365    pub passed_trials: u32,
1366    pub pass_rate: Option<f64>,
1367    pub per_trial_metric: String,
1368    pub per_trial_filter: Option<String>,
1369    pub higher_is_better: bool,
1370}
1371
1372/// The result an Eval client writes for the measurement runtime to consume.
1373#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1374#[serde(deny_unknown_fields)]
1375pub struct EvalClientResult {
1376    /// Result envelope version; clients write `1`. The measurement runtime
1377    /// rejects an eval result whose version is not `1`.
1378    pub schema_version: u32,
1379    pub status: ClientStatus,
1380    pub metrics: BTreeMap<String, f64>,
1381    #[serde(default)]
1382    pub normalized_metrics: BTreeMap<String, EvalNormalizedMetric>,
1383    #[serde(default)]
1384    pub gate: Option<EvalMetricGate>,
1385    #[serde(default)]
1386    pub trial_summary: Option<EvalTrialSummary>,
1387    pub native_command: Vec<String>,
1388    #[serde(default)]
1389    pub native_exit_code: Option<i32>,
1390    #[serde(default)]
1391    pub native_timed_out: bool,
1392    pub raw_artifacts: Vec<RawArtifact>,
1393    pub failure_kind: Option<EvalFailureKind>,
1394    pub error: Option<String>,
1395}
1396
1397/// The result a Bench client writes for the measurement runtime to consume.
1398#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1399#[serde(deny_unknown_fields)]
1400pub struct BenchClientResult {
1401    /// Result envelope version; clients write `1`. The measurement runtime
1402    /// rejects a bench result whose version is not `1`.
1403    pub schema_version: u32,
1404    pub status: ClientStatus,
1405    pub completed_requests: u64,
1406    pub failed_requests: u64,
1407    pub normalization_schema: String,
1408    pub metrics: BTreeMap<String, f64>,
1409    #[serde(default)]
1410    pub request_slo: Option<BenchRequestSloResult>,
1411    #[serde(default)]
1412    pub session_evidence: Option<BenchSessionResultEvidence>,
1413    pub native_command: Vec<String>,
1414    pub native_exit_code: Option<i32>,
1415    #[serde(default)]
1416    pub report_invocations: Vec<BenchNativeInvocation>,
1417    pub raw_artifacts: Vec<RawArtifact>,
1418    pub error: Option<String>,
1419}
1420
1421/// Reconciled native evidence for one session-bounded Bench phase.
1422#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1423#[serde(deny_unknown_fields)]
1424pub struct BenchSessionPhaseSummary {
1425    pub planned_sessions: u32,
1426    pub started_sessions: u32,
1427    pub succeeded_sessions: u32,
1428    pub failed_sessions: u32,
1429    pub planned_requests: u32,
1430    pub attempted_requests: u32,
1431    pub completed_requests: u32,
1432    pub failed_requests: u32,
1433    pub reconciled: bool,
1434}
1435
1436/// Terminal evidence for one admitted runtime session.
1437#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1438#[serde(deny_unknown_fields)]
1439pub struct BenchRuntimeSessionResult {
1440    pub phase: String,
1441    pub runtime_session_id: String,
1442    pub template_identity: String,
1443    pub planned_turns: u32,
1444    pub attempted_turns: u32,
1445    pub status: ClientStatus,
1446    #[serde(default)]
1447    pub failure_classification: Option<String>,
1448    #[serde(default)]
1449    pub diagnostic: Option<String>,
1450    #[serde(default)]
1451    pub failing_turn: Option<u32>,
1452    pub suppressed_later_turns: u32,
1453}
1454
1455/// One native transport request reconciled to a linear-session turn.
1456#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1457#[serde(deny_unknown_fields)]
1458pub struct BenchSessionTurnResult {
1459    pub phase: String,
1460    pub runtime_session_id: String,
1461    pub turn_index: u32,
1462    pub pre_template_content_tokens: u32,
1463    #[serde(default)]
1464    pub observed_prompt_tokens: Option<u32>,
1465    pub native_session_num: u64,
1466    #[serde(default)]
1467    pub preceding_native_session_num: Option<u64>,
1468    #[serde(default)]
1469    pub preceding_terminal_response_receipt_ns: Option<u64>,
1470    #[serde(default)]
1471    pub effective_inter_turn_delay_seconds: Option<f64>,
1472    pub request_start_ns: u64,
1473    #[serde(default)]
1474    pub inter_turn_delay_reconciled: Option<bool>,
1475    #[serde(default)]
1476    pub post_failure_continuation: bool,
1477    pub native_artifact_name: String,
1478}
1479
1480/// Session and transport reconciliation returned by the Bench client.
1481#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1482#[serde(deny_unknown_fields)]
1483pub struct BenchSessionResultEvidence {
1484    pub warmup: BenchSessionPhaseSummary,
1485    pub profiling: BenchSessionPhaseSummary,
1486    pub sessions: Vec<BenchRuntimeSessionResult>,
1487    pub turns: Vec<BenchSessionTurnResult>,
1488    pub population_slice_reconciled: bool,
1489    pub sessions_reconciled: bool,
1490    pub turn_order_reconciled: bool,
1491    pub inter_turn_delays_reconciled: bool,
1492    pub native_requests_reconciled: bool,
1493    pub counts_reconciled: bool,
1494}
1495
1496/// One bounded native post-processing command and its terminal outcome.
1497#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1498#[serde(deny_unknown_fields)]
1499pub struct BenchNativeInvocation {
1500    pub purpose: String,
1501    pub command: Vec<String>,
1502    pub exit_code: Option<i32>,
1503    pub interrupted: bool,
1504    pub timed_out: bool,
1505}
1506
1507/// File-bound request-SLO evidence derived from AIPerf profiling records.
1508#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1509#[serde(deny_unknown_fields)]
1510pub struct BenchRequestSloResult {
1511    pub good_requests: u64,
1512    pub good_request_ratio: f64,
1513    pub goodput: f64,
1514    pub profiling_duration_seconds: f64,
1515    pub profiling_duration_source: String,
1516    pub request_count_reconciled: bool,
1517    #[serde(default)]
1518    pub native_aggregate_good_request_count: Option<u64>,
1519    #[serde(default)]
1520    pub native_aggregate_good_request_count_consistent: Option<bool>,
1521}
1522
1523/// A raw output file a client produced, retained as workload evidence.
1524#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1525#[serde(deny_unknown_fields)]
1526pub struct RawArtifact {
1527    pub name: String,
1528    pub kind: String,
1529    pub path: PathBuf,
1530}
1531
1532/// The schema root for the independently released framework adapter SDK.
1533#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1534#[serde(deny_unknown_fields)]
1535pub struct AdapterProtocol {
1536    pub request: AdapterRequest,
1537    pub response: AdapterResponse,
1538}
1539
1540/// The schema root aggregating the request and result surfaces used by the
1541/// product-owned Eval and Bench measurement clients. Its optional fields are
1542/// code-generation anchors and are never all populated in one message.
1543#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1544#[serde(deny_unknown_fields)]
1545pub struct MeasurementProtocol {
1546    #[serde(default, skip_serializing_if = "Option::is_none")]
1547    pub eval_client_request: Option<EvalClientRequest>,
1548    #[serde(default, skip_serializing_if = "Option::is_none")]
1549    pub eval_client_result: Option<EvalClientResult>,
1550    #[serde(default, skip_serializing_if = "Option::is_none")]
1551    pub bench_client_request: Option<BenchClientRequest>,
1552    #[serde(default, skip_serializing_if = "Option::is_none")]
1553    pub bench_client_result: Option<BenchClientResult>,
1554    #[serde(default, skip_serializing_if = "Option::is_none")]
1555    pub bench_population_preparation_request: Option<BenchPopulationPreparationRequest>,
1556    #[serde(default, skip_serializing_if = "Option::is_none")]
1557    pub bench_population_preparation_result: Option<BenchPopulationPreparationResult>,
1558}