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