1pub mod gate;
18pub mod metric;
19pub mod review;
20
21pub use gate::*;
22pub use metric::*;
23pub use review::*;
24
25use serde::{Deserialize, Serialize};
26use serde_json::Value;
27
28pub const SERVICE_NAME: &str = "agent-evaluate";
29pub const DEFAULT_PORT: u16 = 5150;
30pub const AUDIENCE: &str = "agent-evaluate";
31pub const MAX_RESOURCE_ID_CHARS: usize = 191;
32
33pub const HEALTH_PATH: &str = "/health";
34pub const CASE_VERSIONS_PATH: &str = "/internal/v1/evaluate/case-versions";
35pub const SUITES_PATH: &str = "/internal/v1/evaluate/suites";
36pub const RUNS_PATH: &str = "/internal/v1/evaluate/runs";
37pub const OPERATIONS_PATH: &str = "/internal/v1/evaluate/operations";
38pub const RUN_CASES_PATH: &str = "/internal/v1/evaluate/runs/cases";
39pub const CANCEL_RUN_PATH: &str = "/internal/v1/evaluate/runs/cancel";
40pub const DURABLE_REPORT_PATH: &str = "/internal/v1/evaluate/reports/get";
41pub const COMPARE_REPORTS_PATH: &str = "/internal/v1/evaluate/reports/compare";
42pub const REVIEW_QUEUE_PATH: &str = "/internal/v1/evaluate/reviews";
43pub const CLAIM_REVIEW_PATH: &str = "/internal/v1/evaluate/reviews/claim";
44pub const COMPLETE_REVIEW_PATH: &str = "/internal/v1/evaluate/reviews/complete";
45pub const SAMPLE_REVIEWS_PATH: &str = "/internal/v1/evaluate/reviews/sample";
46pub const METRIC_HEALTH_PATH: &str = "/internal/v1/evaluate/metrics/health";
47pub const METRIC_CANDIDATES_PATH: &str = "/internal/v1/evaluate/metrics/candidates";
48pub const ENVIRONMENT_PROFILES_PATH: &str = "/internal/v1/evaluate/environment-profiles";
49pub const EVALUATOR_VERSIONS_PATH: &str = "/internal/v1/evaluate/evaluator-versions";
50pub const DRIVER_CLAIM_PATH: &str = "/internal/v1/evaluate/driver/cases/claim";
51pub const DRIVER_RENEW_PATH: &str = "/internal/v1/evaluate/driver/cases/renew";
52pub const DRIVER_SUBMIT_PATH: &str = "/internal/v1/evaluate/driver/cases/submit";
53pub const ADMIN_OVERVIEW_PATH: &str = "/internal/v1/evaluate/admin/overview";
54pub const ADMIN_CASES_PATH: &str = "/internal/v1/evaluate/admin/cases";
55pub const ADMIN_SUITES_PATH: &str = "/internal/v1/evaluate/admin/suites";
56pub const ADMIN_RUNS_PATH: &str = "/internal/v1/evaluate/admin/runs";
57pub const ADMIN_RUN_CASES_PATH: &str = "/internal/v1/evaluate/admin/runs/cases";
58pub const ADMIN_CANCEL_RUN_PATH: &str = "/internal/v1/evaluate/admin/runs/cancel";
59pub const ADMIN_REPORT_PATH: &str = "/internal/v1/evaluate/admin/reports/get";
60pub const ADMIN_REVIEWS_PATH: &str = "/internal/v1/evaluate/admin/reviews";
61pub const ADMIN_CLAIM_REVIEW_PATH: &str = "/internal/v1/evaluate/admin/reviews/claim";
62pub const ADMIN_COMPLETE_REVIEW_PATH: &str = "/internal/v1/evaluate/admin/reviews/complete";
63pub const ADMIN_METRIC_HEALTH_PATH: &str = "/internal/v1/evaluate/admin/metrics/health";
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase", deny_unknown_fields)]
71pub struct EvalTurn {
72 pub user: String,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase", deny_unknown_fields)]
77pub struct VerifyCommand {
78 pub name: String,
79 pub command: String,
80 #[serde(default)]
81 pub expected_exit_code: i32,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub output_contains: Option<Vec<String>>,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
91#[serde(rename_all = "camelCase")]
92pub struct EvalReport {
93 pub id: String,
94 pub total_cases: usize,
95 pub passed_cases: usize,
96 pub failed_cases: usize,
97 pub infrastructure_failure_cases: usize,
98 #[serde(default)]
102 pub invalid_case_count: usize,
103 pub non_infrastructure_pass_rate: f64,
104 #[serde(default)]
107 pub metrics: Vec<MetricSummary>,
108 pub case_results: Vec<EvalCaseResult>,
109 pub created_at: String,
110 pub duration_ms: u64,
111 #[serde(default)]
112 pub total_cost_micros: u64,
113 #[serde(default)]
115 pub judge_cost_micros: u64,
116 #[serde(default)]
117 pub p95_case_duration_ms: u64,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct EvalCaseResult {
123 pub case_id: String,
124 pub case_version: String,
127 pub category: String,
128 pub passed: bool,
129 pub infrastructure_failure: bool,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub failure_kind: Option<CaseFailureKind>,
134 pub turns_completed: usize,
135 pub total_turns: usize,
136 #[serde(default)]
138 pub assertion_results: Vec<AssertionVerdict>,
139 #[serde(default)]
141 pub evaluator_verdicts: Vec<EvaluatorVerdict>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub error: Option<String>,
144 pub duration_ms: u64,
145 #[serde(default)]
146 pub cost_micros: u64,
147 #[serde(default)]
150 pub sample_index: u32,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub variant_id: Option<String>,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(rename_all = "snake_case")]
157pub enum CaseFailureKind {
158 InfraFailed,
159 InvalidCase,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct AssertionVerdict {
165 pub rule: String,
166 pub passed: bool,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub detail: Option<String>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(
174 tag = "kind",
175 rename_all = "snake_case",
176 rename_all_fields = "camelCase",
177 deny_unknown_fields
178)]
179pub enum TypedAssertion {
180 OutputContains {
181 turn: usize,
182 values: Vec<String>,
183 #[serde(default)]
184 any: bool,
185 },
186 WorkspaceFile {
187 path: String,
188 #[serde(default)]
189 contains: Vec<String>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
191 max_bytes: Option<usize>,
192 },
193 CommandExit {
194 name: String,
195 command_ref: String,
196 expected_exit_code: i32,
197 },
198 TraceEvent {
199 event_type: String,
200 #[serde(default)]
201 minimum_count: usize,
202 },
203 ArtifactExists {
204 artifact_kind: String,
205 },
206 Plugin {
210 plugin_ref: String,
211 #[serde(default)]
212 config: Value,
213 },
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase", deny_unknown_fields)]
220pub struct VersionedResourceRef {
221 pub id: String,
222 pub version: String,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228#[serde(rename_all = "camelCase", deny_unknown_fields)]
229pub struct EvaluationSubject {
230 pub agent_id: String,
231 pub runtime_id: String,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235#[serde(rename_all = "camelCase", deny_unknown_fields)]
236pub struct RegisterEnvironmentProfileRequest {
237 pub profile_id: String,
238 pub version: String,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub space_id: Option<String>,
243 #[serde(default)]
244 pub metadata: std::collections::BTreeMap<String, String>,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase", deny_unknown_fields)]
249pub struct EnvironmentProfileVersion {
250 pub tenant_id: String,
251 pub project_id: String,
252 pub profile_id: String,
253 pub version: String,
254 pub digest: String,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub space_id: Option<String>,
257 #[serde(default)]
258 pub metadata: std::collections::BTreeMap<String, String>,
259 pub created_at_ms: i64,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
263#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
264pub enum EvaluatorImplementation {
265 BuiltinAssertions { assertions: Vec<TypedAssertion> },
267 LlmJudge {
270 model_ref: String,
273 prompt_digest: String,
275 #[serde(default)]
276 max_cost_micros: u64,
277 },
278}
279
280impl EvaluatorImplementation {
281 pub const fn is_model_graded(&self) -> bool {
282 matches!(self, Self::LlmJudge { .. })
283 }
284}
285
286#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct JudgeCalibration {
290 pub labeled_samples: u64,
291 pub agreed_samples: u64,
292}
293
294impl JudgeCalibration {
295 pub fn agreement(&self) -> f64 {
296 if self.labeled_samples == 0 {
297 0.0
298 } else {
299 self.agreed_samples as f64 / self.labeled_samples as f64
300 }
301 }
302}
303
304pub const JUDGE_PROMOTION_MIN_LABELS: u64 = 50;
306pub const JUDGE_PROMOTION_MIN_AGREEMENT: f64 = 0.9;
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase", deny_unknown_fields)]
310pub struct RegisterEvaluatorVersionRequest {
311 pub evaluator_id: String,
312 pub version: String,
313 pub implementation: EvaluatorImplementation,
314 #[serde(default)]
315 pub metric: MetricSpec,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
319#[serde(rename_all = "camelCase", deny_unknown_fields)]
320pub struct EvaluatorVersion {
321 pub tenant_id: String,
322 pub project_id: String,
323 pub evaluator_id: String,
324 pub version: String,
325 pub digest: String,
326 pub implementation: EvaluatorImplementation,
327 #[serde(default)]
328 pub metric: MetricSpec,
329 pub created_at_ms: i64,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334pub struct EvaluatorVerdict {
335 pub evaluator_id: String,
336 pub evaluator_version: String,
337 pub passed: bool,
338 #[serde(default)]
339 pub class: MetricClass,
340 #[serde(default)]
341 pub decision: MetricDecision,
342 #[serde(default)]
344 pub cost_micros: u64,
345 #[serde(default)]
346 pub assertions: Vec<AssertionVerdict>,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase", deny_unknown_fields)]
354pub struct RegisterEvalCaseRequest {
355 pub case_id: String,
356 pub version: String,
357 pub category: String,
358 #[serde(default)]
359 pub turns: Vec<EvalTurn>,
360 #[serde(default)]
364 pub workspace_files: std::collections::BTreeMap<String, String>,
365 #[serde(default)]
366 pub assertions: Vec<TypedAssertion>,
367 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub environment_profile: Option<VersionedResourceRef>,
370 #[serde(default)]
372 pub evaluator_versions: Vec<VersionedResourceRef>,
373 #[serde(default)]
374 pub verify_commands: Vec<VerifyCommand>,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub fixture_ref: Option<String>,
377 #[serde(default)]
378 pub budget: EvalBudget,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
382#[serde(rename_all = "camelCase", deny_unknown_fields)]
383pub struct EvalCaseVersion {
384 pub tenant_id: String,
385 pub project_id: String,
386 pub case_id: String,
387 pub version: String,
388 pub digest: String,
389 pub category: String,
390 #[serde(default)]
391 pub turns: Vec<EvalTurn>,
392 #[serde(default)]
393 pub workspace_files: std::collections::BTreeMap<String, String>,
394 #[serde(default)]
395 pub assertions: Vec<TypedAssertion>,
396 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub environment_profile: Option<VersionedResourceRef>,
398 #[serde(default)]
399 pub evaluator_versions: Vec<VersionedResourceRef>,
400 #[serde(default)]
401 pub verify_commands: Vec<VerifyCommand>,
402 #[serde(default, skip_serializing_if = "Option::is_none")]
403 pub fixture_ref: Option<String>,
404 #[serde(default)]
405 pub budget: EvalBudget,
406 pub created_at_ms: i64,
407}
408
409impl EvalCaseVersion {
410 pub fn reference(&self) -> VersionedResourceRef {
411 VersionedResourceRef {
412 id: self.case_id.clone(),
413 version: self.version.clone(),
414 }
415 }
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase", deny_unknown_fields)]
420pub struct EvalBudget {
421 pub timeout_ms: u64,
422 pub max_output_bytes: usize,
423 pub max_cost_micros: u64,
424}
425
426impl Default for EvalBudget {
427 fn default() -> Self {
428 Self {
429 timeout_ms: 300_000,
430 max_output_bytes: 8 * 1024 * 1024,
431 max_cost_micros: 0,
432 }
433 }
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
439#[serde(rename_all = "camelCase", deny_unknown_fields)]
440pub struct EvalSuiteVersion {
441 pub tenant_id: String,
442 pub project_id: String,
443 pub suite_id: String,
444 pub version: String,
445 pub manifest_digest: String,
446 pub schema_version: u32,
447 pub cases: Vec<VersionedResourceRef>,
448 pub created_at_ms: i64,
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase", deny_unknown_fields)]
453pub struct RegisterSuiteRequest {
454 pub suite_id: String,
455 pub version: String,
456 pub manifest_digest: String,
457 pub schema_version: u32,
458 pub cases: Vec<VersionedResourceRef>,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
462#[serde(rename_all = "snake_case")]
463pub enum OperationState {
464 Queued,
465 Running,
466 Succeeded,
467 Failed,
468 Canceling,
469 Canceled,
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
473#[serde(rename_all = "snake_case")]
474pub enum CaseRunState {
475 Queued,
476 Running,
477 Passed,
478 QualityFailed,
479 InfraFailed,
480 InvalidCase,
481 Canceled,
482}
483
484#[derive(Debug, Clone, Serialize, Deserialize)]
485#[serde(rename_all = "camelCase", deny_unknown_fields)]
486pub struct CreateEvalRunRequest {
487 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub suite: Option<VersionedResourceRef>,
490 #[serde(default)]
493 pub cases: Vec<VersionedResourceRef>,
494 pub subject_version: String,
495 pub executor_profile: String,
496 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub subject: Option<EvaluationSubject>,
500 #[serde(default)]
501 pub seed: u64,
502 #[serde(default = "default_concurrency")]
503 pub concurrency: usize,
504 #[serde(default)]
505 pub idempotency_key: String,
506 #[serde(default = "default_samples")]
508 pub samples: u32,
509 #[serde(default)]
512 pub experiment_matrix: Vec<ExperimentVariant>,
513}
514
515fn default_concurrency() -> usize {
516 8
517}
518
519fn default_samples() -> u32 {
520 1
521}
522
523#[derive(Debug, Clone, Serialize, Deserialize)]
524#[serde(rename_all = "camelCase", deny_unknown_fields)]
525pub struct ExperimentVariant {
526 pub id: String,
527 pub subject_version: String,
528 pub executor_profile: String,
529 #[serde(default)]
530 pub parameters: Value,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct EvalOperation {
536 pub id: String,
537 pub run_id: String,
538 pub state: OperationState,
539 pub phase: String,
540 pub attempt: u32,
541 pub completed_cases: usize,
542 pub total_cases: usize,
543 #[serde(default, skip_serializing_if = "Option::is_none")]
544 pub report_id: Option<String>,
545 pub created_at_ms: i64,
546 pub updated_at_ms: i64,
547 pub version: i64,
548 #[serde(default, skip_serializing_if = "Option::is_none")]
549 pub next_poll_after_ms: Option<u64>,
550 #[serde(default, skip_serializing_if = "Option::is_none")]
551 pub error_code: Option<String>,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
555#[serde(rename_all = "camelCase", deny_unknown_fields)]
556pub struct GetOperationRequest {
557 pub operation_id: String,
558}
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
561#[serde(rename_all = "camelCase", deny_unknown_fields)]
562pub struct CancelRunRequest {
563 pub run_id: String,
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize)]
567#[serde(rename_all = "camelCase", deny_unknown_fields)]
568pub struct ListCaseRunsRequest {
569 pub run_id: String,
570 #[serde(default, skip_serializing_if = "Option::is_none")]
571 pub cursor: Option<String>,
572 #[serde(default, skip_serializing_if = "Option::is_none")]
573 pub state: Option<CaseRunState>,
574 #[serde(default = "default_case_page")]
575 pub limit: usize,
576}
577
578fn default_case_page() -> usize {
579 50
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct CaseRunSummary {
585 pub id: String,
586 pub run_id: String,
587 pub case_id: String,
588 pub case_version: String,
589 pub position: usize,
590 pub state: CaseRunState,
591 pub attempt: u32,
592 pub duration_ms: u64,
593 #[serde(default, skip_serializing_if = "Option::is_none")]
594 pub error: Option<String>,
595 #[serde(default)]
596 pub evidence_refs: Vec<EvidenceRef>,
597 #[serde(default)]
598 pub sample_index: u32,
599 #[serde(default, skip_serializing_if = "Option::is_none")]
600 pub variant_id: Option<String>,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
604#[serde(rename_all = "camelCase")]
605pub struct CaseRunPage {
606 pub items: Vec<CaseRunSummary>,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 pub next_cursor: Option<String>,
609}
610
611#[derive(Debug, Clone, Serialize, Deserialize)]
612#[serde(rename_all = "camelCase", deny_unknown_fields)]
613pub struct EvalAccessScope {
614 pub tenant_id: String,
615 pub project_id: String,
616 pub subject: String,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
620#[serde(rename_all = "camelCase", deny_unknown_fields)]
621pub struct SandboxExecutionRequest {
622 pub scope: EvalAccessScope,
626 pub operation_id: String,
627 pub run_id: String,
628 pub case_run_id: String,
629 pub subject_version: String,
630 pub executor_profile: String,
631 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub subject: Option<EvaluationSubject>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
634 pub environment_profile: Option<EnvironmentProfileVersion>,
635 #[serde(default)]
636 pub evaluator_versions: Vec<EvaluatorVersion>,
637 pub seed: u64,
638 pub case: EvalCaseVersion,
639 pub fencing_token: i64,
640 #[serde(default)]
641 pub sample_index: u32,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 pub variant: Option<ExperimentVariant>,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize)]
647#[serde(rename_all = "camelCase", deny_unknown_fields)]
648pub struct PluginEvaluationEvidence {
649 pub plugin_ref: String,
650 pub passed: bool,
651 #[serde(default)]
652 pub detail: String,
653 pub attestation: String,
656}
657
658#[derive(Debug, Clone, Serialize, Deserialize)]
661#[serde(rename_all = "camelCase", deny_unknown_fields)]
662pub struct JudgeEvaluationEvidence {
663 pub evaluator_id: String,
664 pub evaluator_version: String,
665 pub passed: bool,
666 pub model_ref: String,
668 #[serde(default)]
669 pub detail: String,
670 #[serde(default)]
671 pub cost_micros: u64,
672 pub attestation: String,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
677#[serde(rename_all = "camelCase", deny_unknown_fields)]
678pub struct EvidenceRef {
679 pub reference: String,
680 pub tenant_id: String,
681 pub project_id: String,
682 pub run_id: String,
683 pub case_run_id: String,
684 pub digest: String,
686 pub size_bytes: u64,
687 pub attestation: String,
689}
690
691#[derive(Debug, Clone, Default, Serialize, Deserialize)]
694#[serde(rename_all = "camelCase", deny_unknown_fields)]
695pub struct SandboxExecutionEvidence {
696 #[serde(default)]
697 pub responses: Vec<String>,
698 #[serde(default)]
699 pub workspace_files: std::collections::BTreeMap<String, String>,
700 #[serde(default)]
701 pub command_exit_codes: std::collections::BTreeMap<String, i32>,
702 #[serde(default)]
703 pub trace_event_counts: std::collections::BTreeMap<String, usize>,
704 #[serde(default)]
705 pub artifact_kinds: Vec<String>,
706 #[serde(default)]
708 pub evidence_refs: Vec<EvidenceRef>,
709 #[serde(default)]
710 pub plugin_evaluations: Vec<PluginEvaluationEvidence>,
711 #[serde(default)]
712 pub judge_evaluations: Vec<JudgeEvaluationEvidence>,
713 #[serde(default, skip_serializing_if = "Option::is_none")]
714 pub infrastructure_error: Option<String>,
715 #[serde(default, skip_serializing_if = "Option::is_none")]
719 pub invalid_case_error: Option<String>,
720 #[serde(default)]
721 pub duration_ms: u64,
722 #[serde(default)]
724 pub cost_micros: u64,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize)]
731#[serde(rename_all = "camelCase", deny_unknown_fields)]
732pub struct ClaimEvaluationCaseRequest {
733 pub evaluate_task_id: String,
734 #[serde(default = "default_driver_lease_ms")]
735 pub lease_ms: i64,
736}
737
738fn default_driver_lease_ms() -> i64 {
739 60_000
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize)]
743#[serde(rename_all = "camelCase", deny_unknown_fields)]
744pub struct EvaluationCaseLease {
745 pub execution: SandboxExecutionRequest,
746 pub lease_until_ms: i64,
747}
748
749#[derive(Debug, Clone, Serialize, Deserialize)]
750#[serde(rename_all = "camelCase", deny_unknown_fields)]
751pub struct ClaimEvaluationCaseResponse {
752 pub operation: EvalOperation,
753 #[serde(default, skip_serializing_if = "Option::is_none")]
754 pub case: Option<EvaluationCaseLease>,
755}
756
757#[derive(Debug, Clone, Serialize, Deserialize)]
758#[serde(rename_all = "camelCase", deny_unknown_fields)]
759pub struct RenewEvaluationCaseRequest {
760 pub evaluate_task_id: String,
761 pub case_run_id: String,
762 pub fencing_token: i64,
763 #[serde(default = "default_driver_lease_ms")]
764 pub lease_ms: i64,
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize)]
768#[serde(rename_all = "camelCase", deny_unknown_fields)]
769pub struct RenewEvaluationCaseResponse {
770 pub renewed: bool,
771 pub lease_until_ms: i64,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
775#[serde(rename_all = "camelCase", deny_unknown_fields)]
776pub struct SubmitEvaluationEvidenceRequest {
777 pub evaluate_task_id: String,
778 pub case_run_id: String,
779 pub fencing_token: i64,
780 pub evidence: SandboxExecutionEvidence,
781}
782
783#[derive(Debug, Clone, Serialize, Deserialize)]
784#[serde(rename_all = "camelCase", deny_unknown_fields)]
785pub struct SubmitEvaluationEvidenceResponse {
786 pub accepted: bool,
787 pub operation: EvalOperation,
788}
789
790#[derive(Debug, Clone, Serialize, Deserialize)]
791#[serde(rename_all = "camelCase", deny_unknown_fields)]
792pub struct GetReportRequest {
793 pub report_id: String,
794}
795
796pub const DEFAULT_ADMIN_PAGE_SIZE: usize = 50;
801pub const MAX_ADMIN_PAGE_SIZE: usize = 200;
802
803fn default_admin_page() -> usize {
804 DEFAULT_ADMIN_PAGE_SIZE
805}
806
807#[derive(Debug, Clone, Serialize, Deserialize)]
808#[serde(rename_all = "camelCase", deny_unknown_fields)]
809pub struct ListAdminCasesRequest {
810 #[serde(default, skip_serializing_if = "Option::is_none")]
811 pub cursor: Option<String>,
812 #[serde(default = "default_admin_page")]
813 pub limit: usize,
814}
815
816impl Default for ListAdminCasesRequest {
817 fn default() -> Self {
818 Self {
819 cursor: None,
820 limit: DEFAULT_ADMIN_PAGE_SIZE,
821 }
822 }
823}
824
825#[derive(Debug, Clone, Serialize, Deserialize)]
826#[serde(rename_all = "camelCase", deny_unknown_fields)]
827pub struct ListAdminSuitesRequest {
828 #[serde(default, skip_serializing_if = "Option::is_none")]
829 pub cursor: Option<String>,
830 #[serde(default = "default_admin_page")]
831 pub limit: usize,
832}
833
834impl Default for ListAdminSuitesRequest {
835 fn default() -> Self {
836 Self {
837 cursor: None,
838 limit: DEFAULT_ADMIN_PAGE_SIZE,
839 }
840 }
841}
842
843#[derive(Debug, Clone, Serialize, Deserialize)]
844#[serde(rename_all = "camelCase", deny_unknown_fields)]
845pub struct ListAdminRunsRequest {
846 #[serde(default, skip_serializing_if = "Option::is_none")]
847 pub cursor: Option<String>,
848 #[serde(default, skip_serializing_if = "Option::is_none")]
849 pub state: Option<OperationState>,
850 #[serde(default = "default_admin_page")]
851 pub limit: usize,
852}
853
854impl Default for ListAdminRunsRequest {
855 fn default() -> Self {
856 Self {
857 cursor: None,
858 state: None,
859 limit: DEFAULT_ADMIN_PAGE_SIZE,
860 }
861 }
862}
863
864#[derive(Debug, Clone, Serialize, Deserialize)]
865#[serde(rename_all = "camelCase", deny_unknown_fields)]
866pub struct ListAdminReviewsRequest {
867 #[serde(default, skip_serializing_if = "Option::is_none")]
868 pub cursor: Option<String>,
869 #[serde(default, skip_serializing_if = "Option::is_none")]
870 pub state: Option<ReviewState>,
871 #[serde(default = "default_admin_page")]
872 pub limit: usize,
873}
874
875impl Default for ListAdminReviewsRequest {
876 fn default() -> Self {
877 Self {
878 cursor: None,
879 state: None,
880 limit: DEFAULT_ADMIN_PAGE_SIZE,
881 }
882 }
883}
884
885#[derive(Debug, Clone, Serialize, Deserialize)]
886#[serde(rename_all = "camelCase")]
887pub struct EvaluateAdminOverview {
888 pub case_versions: u64,
889 pub suite_versions: u64,
890 pub run_states: std::collections::BTreeMap<String, u64>,
891 pub case_states: std::collections::BTreeMap<String, u64>,
892 pub review_states: std::collections::BTreeMap<String, u64>,
893}
894
895#[derive(Debug, Clone, Serialize, Deserialize)]
896#[serde(rename_all = "camelCase")]
897pub struct AdminCaseSummary {
898 pub case_id: String,
899 pub version: String,
900 pub digest: String,
901 pub category: String,
902 pub turns: usize,
903 pub evaluator_versions: Vec<VersionedResourceRef>,
904 #[serde(default, skip_serializing_if = "Option::is_none")]
905 pub environment_profile: Option<VersionedResourceRef>,
906 pub created_at_ms: i64,
907}
908
909#[derive(Debug, Clone, Serialize, Deserialize)]
910#[serde(rename_all = "camelCase")]
911pub struct AdminCasePage {
912 pub items: Vec<AdminCaseSummary>,
913 #[serde(default, skip_serializing_if = "Option::is_none")]
914 pub next_cursor: Option<String>,
915}
916
917#[derive(Debug, Clone, Serialize, Deserialize)]
918#[serde(rename_all = "camelCase")]
919pub struct AdminSuiteSummary {
920 pub suite_id: String,
921 pub version: String,
922 pub manifest_digest: String,
923 pub schema_version: u32,
924 pub case_count: usize,
925 pub created_at_ms: i64,
926}
927
928#[derive(Debug, Clone, Serialize, Deserialize)]
929#[serde(rename_all = "camelCase")]
930pub struct AdminSuitePage {
931 pub items: Vec<AdminSuiteSummary>,
932 #[serde(default, skip_serializing_if = "Option::is_none")]
933 pub next_cursor: Option<String>,
934}
935
936#[derive(Debug, Clone, Serialize, Deserialize)]
937#[serde(rename_all = "camelCase")]
938pub struct AdminRunSummary {
939 pub run_id: String,
940 pub operation_id: String,
941 #[serde(default, skip_serializing_if = "Option::is_none")]
943 pub suite: Option<VersionedResourceRef>,
944 pub subject_version: String,
945 pub executor_profile: String,
946 #[serde(default, skip_serializing_if = "Option::is_none")]
947 pub subject: Option<EvaluationSubject>,
948 pub state: OperationState,
949 pub phase: String,
950 pub completed_cases: usize,
951 pub total_cases: usize,
952 #[serde(default, skip_serializing_if = "Option::is_none")]
953 pub report_id: Option<String>,
954 pub created_at_ms: i64,
955 pub updated_at_ms: i64,
956 pub version: i64,
957}
958
959#[derive(Debug, Clone, Serialize, Deserialize)]
960#[serde(rename_all = "camelCase")]
961pub struct AdminRunPage {
962 pub items: Vec<AdminRunSummary>,
963 #[serde(default, skip_serializing_if = "Option::is_none")]
964 pub next_cursor: Option<String>,
965}
966
967#[derive(Debug, Clone, Serialize, Deserialize)]
968#[serde(rename_all = "camelCase")]
969pub struct AdminReviewPage {
970 pub items: Vec<HumanReview>,
971 #[serde(default, skip_serializing_if = "Option::is_none")]
972 pub next_cursor: Option<String>,
973}
974
975#[derive(Debug, Clone, Serialize, Deserialize)]
977pub struct ApiError {
978 pub status: u16,
979 pub message: String,
980}
981
982#[cfg(test)]
983mod tests {
984 use super::{
985 DEFAULT_ADMIN_PAGE_SIZE, ListAdminCasesRequest, ListAdminReviewsRequest,
986 ListAdminRunsRequest, ListAdminSuitesRequest, TypedAssertion,
987 };
988 use serde_json::json;
989
990 #[test]
991 fn typed_assertion_uses_snake_case_tags_and_camel_case_fields() {
992 let assertion = TypedAssertion::TraceEvent {
993 event_type: "task.completed".into(),
994 minimum_count: 1,
995 };
996 let value = serde_json::to_value(&assertion).unwrap();
997 assert_eq!(
998 value,
999 json!({
1000 "kind": "trace_event",
1001 "eventType": "task.completed",
1002 "minimumCount": 1
1003 })
1004 );
1005 assert!(serde_json::from_value::<TypedAssertion>(value).is_ok());
1006 }
1007
1008 #[test]
1009 fn admin_queries_use_the_same_bounded_default_in_rust_and_serde() {
1010 assert_eq!(
1011 ListAdminCasesRequest::default().limit,
1012 DEFAULT_ADMIN_PAGE_SIZE
1013 );
1014 assert_eq!(
1015 ListAdminSuitesRequest::default().limit,
1016 DEFAULT_ADMIN_PAGE_SIZE
1017 );
1018 assert_eq!(
1019 ListAdminRunsRequest::default().limit,
1020 DEFAULT_ADMIN_PAGE_SIZE
1021 );
1022 assert_eq!(
1023 ListAdminReviewsRequest::default().limit,
1024 DEFAULT_ADMIN_PAGE_SIZE
1025 );
1026 let decoded: ListAdminRunsRequest = serde_json::from_value(json!({})).unwrap();
1027 assert_eq!(decoded.limit, DEFAULT_ADMIN_PAGE_SIZE);
1028 }
1029}