1use serde::{Deserialize, Serialize};
17use serde_json::Value;
18
19pub const SERVICE_NAME: &str = "agent-evaluate";
20pub const DEFAULT_PORT: u16 = 5150;
21pub const AUDIENCE: &str = "agent-evaluate";
22pub const SERVICE_TOKEN_ENV: &str = "AGENT_EVALUATE_SERVICE_TOKEN";
23pub const MAX_RESOURCE_ID_CHARS: usize = 191;
24
25pub const HEALTH_PATH: &str = "/health";
26pub const RUN_EVAL_PATH: &str = "/internal/v1/evaluate/run";
27pub const LIST_CASES_PATH: &str = "/internal/v1/evaluate/cases";
28pub const GET_CASE_PATH: &str = "/internal/v1/evaluate/cases/get";
29pub const GET_REPORT_PATH: &str = "/internal/v1/evaluate/reports";
30pub const LOAD_CASES_PATH: &str = "/internal/v1/evaluate/cases/load";
31pub const SUITES_PATH: &str = "/internal/v1/evaluate/suites";
32pub const RUNS_PATH: &str = "/internal/v1/evaluate/runs";
33pub const OPERATIONS_PATH: &str = "/internal/v1/evaluate/operations";
34pub const RUN_CASES_PATH: &str = "/internal/v1/evaluate/runs/cases";
35pub const CANCEL_RUN_PATH: &str = "/internal/v1/evaluate/runs/cancel";
36pub const DURABLE_REPORT_PATH: &str = "/internal/v1/evaluate/reports/get";
37pub const COMPARE_REPORTS_PATH: &str = "/internal/v1/evaluate/reports/compare";
38pub const REVIEW_QUEUE_PATH: &str = "/internal/v1/evaluate/reviews";
39pub const CLAIM_REVIEW_PATH: &str = "/internal/v1/evaluate/reviews/claim";
40pub const COMPLETE_REVIEW_PATH: &str = "/internal/v1/evaluate/reviews/complete";
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct EvalCaseFile {
49 pub schema_version: i32,
50 pub cases: Vec<EvalCase>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct EvalCase {
56 pub id: String,
57 pub category: String,
58 #[serde(default)]
59 pub notes: String,
60 pub turns: Vec<EvalTurn>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub workspace_files: Option<Value>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub oracle: Option<EvalOracle>,
65 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub verify_commands: Option<Vec<VerifyCommand>>,
67 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub trace_assertions: Option<Value>,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub outcome_assertions: Option<Value>,
71 #[serde(default)]
72 pub expects_artifact: bool,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub generated_workspace_files: Option<Value>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub workspace_copies: Option<Value>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub workspace_patches: Option<Value>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct EvalTurn {
84 pub user: String,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct EvalOracle {
90 pub kind: String,
91 #[serde(default)]
92 pub expected_values: Vec<Value>,
93 #[serde(default)]
94 pub notes: String,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct VerifyCommand {
100 pub name: String,
101 pub command: String,
102 #[serde(default)]
103 pub expected_exit_code: i32,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub output_contains: Option<Vec<String>>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct RunEvalRequest {
115 #[serde(default)]
116 pub case_ids: Vec<String>,
117 #[serde(default)]
118 pub categories: Vec<String>,
119 #[serde(default)]
120 pub runtime_types: Vec<String>,
121 #[serde(default)]
122 pub max_turns: usize,
123 #[serde(default)]
124 pub turn_timeout_ms: u64,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct EvalReport {
130 pub id: String,
131 pub total_cases: usize,
132 pub passed_cases: usize,
133 pub failed_cases: usize,
134 pub infrastructure_failure_cases: usize,
135 #[serde(default)]
139 pub invalid_case_count: usize,
140 pub non_infrastructure_pass_rate: f64,
141 pub case_results: Vec<EvalCaseResult>,
142 pub created_at: String,
143 pub duration_ms: u64,
144 #[serde(default)]
145 pub total_cost_micros: u64,
146 #[serde(default)]
147 pub p95_case_duration_ms: u64,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct EvalCaseResult {
153 pub case_id: String,
154 pub category: String,
155 pub passed: bool,
156 pub infrastructure_failure: bool,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub failure_kind: Option<CaseFailureKind>,
161 pub turns_completed: usize,
162 pub total_turns: usize,
163 #[serde(default)]
164 pub oracle_results: Vec<OracleVerdict>,
165 #[serde(default)]
166 pub verify_results: Vec<VerifyCommandResult>,
167 #[serde(default)]
168 pub trace_assertion_results: Vec<AssertionVerdict>,
169 #[serde(default)]
170 pub outcome_assertion_results: Vec<AssertionVerdict>,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub error: Option<String>,
173 pub duration_ms: u64,
174 #[serde(default)]
175 pub cost_micros: u64,
176 #[serde(default)]
180 pub sample_index: u32,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub variant_id: Option<String>,
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
186#[serde(rename_all = "snake_case")]
187pub enum CaseFailureKind {
188 InfraFailed,
189 InvalidCase,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct OracleVerdict {
195 pub expected: String,
196 pub actual: String,
197 pub matched: bool,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub struct VerifyCommandResult {
203 pub name: String,
204 pub exit_code: i32,
205 pub expected_exit_code: i32,
206 pub passed: bool,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub output: Option<String>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct AssertionVerdict {
214 pub rule: String,
215 pub passed: bool,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub detail: Option<String>,
218}
219
220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase", deny_unknown_fields)]
227pub struct OutcomeAssertionSpec {
228 #[serde(default)]
229 pub turns: Vec<TurnAssertionSpec>,
230 #[serde(default)]
231 pub workspace_files: Vec<WorkspaceFileAssertionSpec>,
232 #[serde(default)]
233 pub required_changed_paths: Vec<String>,
234 #[serde(default)]
235 pub forbid_workspace_changes: bool,
236}
237
238#[derive(Debug, Clone, Default, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase", deny_unknown_fields)]
240pub struct TurnAssertionSpec {
241 pub index: usize,
242 #[serde(default)]
243 pub contains: Vec<String>,
244 #[serde(default)]
245 pub contains_any: Vec<Vec<String>>,
246}
247
248#[derive(Debug, Clone, Default, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase", deny_unknown_fields)]
250pub struct WorkspaceFileAssertionSpec {
251 pub path: String,
252 #[serde(default)]
253 pub contains: Vec<String>,
254 #[serde(default)]
255 pub contains_any: Vec<Vec<String>>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub max_chars: Option<usize>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 pub max_lines: Option<usize>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
266pub enum TypedAssertion {
267 OutputContains {
268 turn: usize,
269 values: Vec<String>,
270 #[serde(default)]
271 any: bool,
272 },
273 WorkspaceFile {
274 path: String,
275 #[serde(default)]
276 contains: Vec<String>,
277 #[serde(default, skip_serializing_if = "Option::is_none")]
278 max_bytes: Option<usize>,
279 },
280 CommandExit {
281 name: String,
282 command_ref: String,
283 expected_exit_code: i32,
284 },
285 TraceEvent {
286 event_type: String,
287 #[serde(default)]
288 minimum_count: usize,
289 },
290 ArtifactExists {
291 artifact_kind: String,
292 },
293 Plugin {
297 plugin_ref: String,
298 #[serde(default)]
299 config: Value,
300 },
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase", deny_unknown_fields)]
305pub struct VersionedEvalCase {
306 pub id: String,
307 pub category: String,
308 #[serde(default)]
309 pub turns: Vec<EvalTurn>,
310 #[serde(default)]
311 pub assertions: Vec<TypedAssertion>,
312 #[serde(default)]
313 pub verify_commands: Vec<VerifyCommand>,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
315 pub fixture_ref: Option<String>,
316 #[serde(default)]
317 pub budget: EvalBudget,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase", deny_unknown_fields)]
322pub struct EvalBudget {
323 pub timeout_ms: u64,
324 pub max_output_bytes: usize,
325 pub max_cost_micros: u64,
326}
327
328impl Default for EvalBudget {
329 fn default() -> Self {
330 Self {
331 timeout_ms: 300_000,
332 max_output_bytes: 8 * 1024 * 1024,
333 max_cost_micros: 0,
334 }
335 }
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase", deny_unknown_fields)]
340pub struct EvalSuiteVersion {
341 pub tenant_id: String,
342 pub project_id: String,
343 pub suite_id: String,
344 pub version: String,
345 pub manifest_digest: String,
346 pub schema_version: u32,
347 pub cases: Vec<VersionedEvalCase>,
348 pub created_at_ms: i64,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase", deny_unknown_fields)]
353pub struct RegisterSuiteRequest {
354 pub suite_id: String,
355 pub version: String,
356 pub manifest_digest: String,
357 pub schema_version: u32,
358 pub cases: Vec<VersionedEvalCase>,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362#[serde(rename_all = "snake_case")]
363pub enum OperationState {
364 Queued,
365 Running,
366 Succeeded,
367 Failed,
368 Canceling,
369 Canceled,
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
373#[serde(rename_all = "snake_case")]
374pub enum CaseRunState {
375 Queued,
376 Running,
377 Passed,
378 QualityFailed,
379 InfraFailed,
380 InvalidCase,
381 Canceled,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
385#[serde(rename_all = "camelCase", deny_unknown_fields)]
386pub struct CreateEvalRunRequest {
387 pub suite_id: String,
388 pub suite_version: String,
389 pub subject_version: String,
390 pub executor_profile: String,
391 #[serde(default)]
392 pub case_ids: Vec<String>,
393 #[serde(default)]
394 pub seed: u64,
395 #[serde(default = "default_concurrency")]
396 pub concurrency: usize,
397 #[serde(default)]
398 pub idempotency_key: String,
399 #[serde(default = "default_samples")]
401 pub samples: u32,
402 #[serde(default)]
405 pub experiment_matrix: Vec<ExperimentVariant>,
406}
407
408fn default_concurrency() -> usize {
409 8
410}
411
412fn default_samples() -> u32 {
413 1
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
417#[serde(rename_all = "camelCase", deny_unknown_fields)]
418pub struct ExperimentVariant {
419 pub id: String,
420 pub subject_version: String,
421 pub executor_profile: String,
422 #[serde(default)]
423 pub parameters: Value,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct EvalOperation {
429 pub id: String,
430 pub run_id: String,
431 pub state: OperationState,
432 pub phase: String,
433 pub attempt: u32,
434 pub completed_cases: usize,
435 pub total_cases: usize,
436 #[serde(default, skip_serializing_if = "Option::is_none")]
437 pub report_id: Option<String>,
438 pub created_at_ms: i64,
439 pub updated_at_ms: i64,
440 pub version: i64,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub next_poll_after_ms: Option<u64>,
443 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub error_code: Option<String>,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase", deny_unknown_fields)]
449pub struct GetOperationRequest {
450 pub operation_id: String,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(rename_all = "camelCase", deny_unknown_fields)]
455pub struct CancelRunRequest {
456 pub run_id: String,
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
460#[serde(rename_all = "camelCase", deny_unknown_fields)]
461pub struct ListCaseRunsRequest {
462 pub run_id: String,
463 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub cursor: Option<String>,
465 #[serde(default, skip_serializing_if = "Option::is_none")]
466 pub state: Option<CaseRunState>,
467 #[serde(default = "default_case_page")]
468 pub limit: usize,
469}
470
471fn default_case_page() -> usize {
472 50
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct CaseRunSummary {
478 pub id: String,
479 pub run_id: String,
480 pub case_id: String,
481 pub position: usize,
482 pub state: CaseRunState,
483 pub attempt: u32,
484 pub duration_ms: u64,
485 #[serde(default, skip_serializing_if = "Option::is_none")]
486 pub error: Option<String>,
487 #[serde(default)]
488 pub evidence_refs: Vec<EvidenceRef>,
489 #[serde(default)]
490 pub sample_index: u32,
491 #[serde(default, skip_serializing_if = "Option::is_none")]
492 pub variant_id: Option<String>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
496#[serde(rename_all = "camelCase")]
497pub struct CaseRunPage {
498 pub items: Vec<CaseRunSummary>,
499 #[serde(default, skip_serializing_if = "Option::is_none")]
500 pub next_cursor: Option<String>,
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
504#[serde(rename_all = "camelCase", deny_unknown_fields)]
505pub struct EvalAccessScope {
506 pub tenant_id: String,
507 pub project_id: String,
508 pub subject: String,
509}
510
511#[derive(Debug, Clone, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase", deny_unknown_fields)]
513pub struct SandboxExecutionRequest {
514 pub scope: EvalAccessScope,
518 pub operation_id: String,
519 pub run_id: String,
520 pub case_run_id: String,
521 pub subject_version: String,
522 pub executor_profile: String,
523 pub seed: u64,
524 pub case: VersionedEvalCase,
525 pub fencing_token: i64,
526 #[serde(default)]
527 pub sample_index: u32,
528 #[serde(default, skip_serializing_if = "Option::is_none")]
529 pub variant: Option<ExperimentVariant>,
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
533#[serde(rename_all = "camelCase", deny_unknown_fields)]
534pub struct PluginEvaluationEvidence {
535 pub plugin_ref: String,
536 pub passed: bool,
537 #[serde(default)]
538 pub detail: String,
539 pub attestation: String,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
546#[serde(rename_all = "camelCase", deny_unknown_fields)]
547pub struct EvidenceRef {
548 pub reference: String,
549 pub tenant_id: String,
550 pub project_id: String,
551 pub run_id: String,
552 pub case_run_id: String,
553 pub digest: String,
555 pub size_bytes: u64,
556 pub attestation: String,
558}
559
560#[derive(Debug, Clone, Default, Serialize, Deserialize)]
563#[serde(rename_all = "camelCase", deny_unknown_fields)]
564pub struct SandboxExecutionEvidence {
565 #[serde(default)]
566 pub responses: Vec<String>,
567 #[serde(default)]
568 pub workspace_files: std::collections::BTreeMap<String, String>,
569 #[serde(default)]
570 pub command_exit_codes: std::collections::BTreeMap<String, i32>,
571 #[serde(default)]
572 pub trace_event_counts: std::collections::BTreeMap<String, usize>,
573 #[serde(default)]
574 pub artifact_kinds: Vec<String>,
575 #[serde(default)]
577 pub evidence_refs: Vec<EvidenceRef>,
578 #[serde(default)]
579 pub plugin_evaluations: Vec<PluginEvaluationEvidence>,
580 #[serde(default, skip_serializing_if = "Option::is_none")]
581 pub infrastructure_error: Option<String>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
586 pub invalid_case_error: Option<String>,
587 #[serde(default)]
588 pub duration_ms: u64,
589 #[serde(default)]
591 pub cost_micros: u64,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
595#[serde(rename_all = "camelCase")]
596pub struct ListCasesRequest {
597 #[serde(default, skip_serializing_if = "Option::is_none")]
598 pub category: Option<String>,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
602#[serde(rename_all = "camelCase")]
603pub struct ListCasesResponse {
604 pub cases: Vec<EvalCaseSummary>,
605}
606
607#[derive(Debug, Clone, Serialize, Deserialize)]
608#[serde(rename_all = "camelCase")]
609pub struct EvalCaseSummary {
610 pub id: String,
611 pub category: String,
612 pub notes: String,
613 pub turns: usize,
614 #[serde(default)]
615 pub has_oracle: bool,
616 #[serde(default)]
617 pub has_verify_commands: bool,
618 #[serde(default)]
619 pub expects_artifact: bool,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize)]
623#[serde(rename_all = "camelCase")]
624pub struct GetCaseRequest {
625 pub case_id: String,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize)]
629#[serde(rename_all = "camelCase")]
630pub struct LoadCasesRequest {
631 pub file_path: String,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize)]
635#[serde(rename_all = "camelCase")]
636pub struct GetReportRequest {
637 pub report_id: String,
638}
639
640#[derive(Debug, Clone, Serialize, Deserialize)]
641#[serde(rename_all = "camelCase", deny_unknown_fields)]
642pub struct CompareReportsRequest {
643 pub report_id: String,
644 pub baseline_report_id: String,
645 #[serde(default)]
646 pub minimum_pass_rate: f64,
647 #[serde(default = "default_max_infra_rate")]
648 pub maximum_infra_failure_rate: f64,
649 #[serde(default = "default_significance")]
650 pub significance_level: f64,
651 #[serde(default)]
652 pub minimum_effect: f64,
653}
654
655fn default_max_infra_rate() -> f64 {
656 1.0
657}
658
659fn default_significance() -> f64 {
660 0.05
661}
662
663#[derive(Debug, Clone, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub struct CaseOutcomeDiff {
666 pub case_id: String,
667 #[serde(default, skip_serializing_if = "Option::is_none")]
668 pub variant_id: Option<String>,
669 pub baseline_passed: bool,
670 pub current_passed: bool,
671 pub flaky_signal: bool,
672 #[serde(default)]
673 pub historical_flake_rate: f64,
674 #[serde(default)]
675 pub historical_samples: u64,
676}
677
678#[derive(Debug, Clone, Serialize, Deserialize)]
679#[serde(rename_all = "camelCase")]
680pub struct ReportComparison {
681 pub report_id: String,
682 pub baseline_report_id: String,
683 pub pass_rate_delta: f64,
684 pub infra_failure_rate_delta: f64,
685 pub pass_rate_confidence_low: f64,
686 pub pass_rate_confidence_high: f64,
687 pub effect_confidence_low: f64,
688 pub effect_confidence_high: f64,
689 pub p_value: f64,
690 pub statistically_significant: bool,
691 pub changed_cases: Vec<CaseOutcomeDiff>,
692 pub gate_passed: bool,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
696#[serde(rename_all = "snake_case")]
697pub enum ReviewState {
698 Queued,
699 Claimed,
700 Approved,
701 Rejected,
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize)]
705#[serde(rename_all = "camelCase")]
706pub struct HumanReview {
707 pub id: String,
708 pub run_id: String,
709 pub case_run_id: String,
710 pub state: ReviewState,
711 pub reason: String,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub reviewer: Option<String>,
714 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub decision_note: Option<String>,
716 pub fencing_token: i64,
717 pub created_at_ms: i64,
718 pub updated_at_ms: i64,
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize)]
722#[serde(rename_all = "camelCase", deny_unknown_fields)]
723pub struct EnqueueReviewRequest {
724 pub run_id: String,
725 pub case_run_id: String,
726 pub reason: String,
727}
728
729#[derive(Debug, Clone, Serialize, Deserialize)]
730#[serde(rename_all = "camelCase", deny_unknown_fields)]
731pub struct ClaimReviewRequest {
732 #[serde(default = "default_review_lease")]
733 pub lease_ms: i64,
734}
735
736fn default_review_lease() -> i64 {
737 300_000
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize)]
741#[serde(rename_all = "camelCase", deny_unknown_fields)]
742pub struct CompleteReviewRequest {
743 pub review_id: String,
744 pub fencing_token: i64,
745 pub approved: bool,
746 #[serde(default)]
747 pub note: String,
748}
749
750#[derive(Debug, Clone, Serialize, Deserialize)]
752pub struct ApiError {
753 pub status: u16,
754 pub message: String,
755}