Skip to main content

runtime_core/
lib.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
5#[serde(transparent)]
6pub struct DiagnosticCode(pub String);
7
8impl DiagnosticCode {
9    pub fn new(value: impl Into<String>) -> Self {
10        Self(value.into())
11    }
12
13    pub fn as_str(&self) -> &str {
14        &self.0
15    }
16}
17
18impl From<&str> for DiagnosticCode {
19    fn from(value: &str) -> Self {
20        Self(value.to_string())
21    }
22}
23
24impl From<String> for DiagnosticCode {
25    fn from(value: String) -> Self {
26        Self(value)
27    }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32pub struct Diagnostic {
33    pub severity: DiagnosticSeverity,
34    pub code: DiagnosticCode,
35    pub message: String,
36    pub source: Option<String>,
37    pub help: Option<String>,
38}
39
40impl Diagnostic {
41    pub fn new(
42        severity: DiagnosticSeverity,
43        code: impl Into<DiagnosticCode>,
44        message: impl Into<String>,
45    ) -> Self {
46        Self {
47            severity,
48            code: code.into(),
49            message: message.into(),
50            source: None,
51            help: None,
52        }
53    }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
57#[serde(rename_all = "camelCase")]
58pub enum DiagnosticSeverity {
59    Info,
60    Warning,
61    Error,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase")]
66pub struct RuntimeCapabilities {
67    pub native: bool,
68    pub server: bool,
69    pub wasm: bool,
70    pub mobile: MobileCapability,
71    pub requirements: Vec<RuntimeRequirement>,
72    pub max_recommended_input_bytes: Option<u64>,
73}
74
75impl RuntimeCapabilities {
76    pub fn pure_rust() -> Self {
77        Self {
78            native: true,
79            server: true,
80            wasm: true,
81            mobile: MobileCapability::Wasm,
82            requirements: Vec::new(),
83            max_recommended_input_bytes: None,
84        }
85    }
86
87    pub fn with_max_recommended_input_bytes(mut self, bytes: u64) -> Self {
88        self.max_recommended_input_bytes = Some(bytes);
89        self
90    }
91
92    pub fn with_requirement(
93        mut self,
94        name: impl Into<String>,
95        description: impl Into<String>,
96        required: bool,
97    ) -> Self {
98        self.requirements.push(RuntimeRequirement {
99            name: name.into(),
100            description: Some(description.into()),
101            required,
102        });
103        self
104    }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108#[serde(rename_all = "camelCase")]
109pub enum MobileCapability {
110    Native,
111    Wasm,
112    ApiOnly,
113    Unsupported,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117#[serde(rename_all = "camelCase")]
118pub struct RuntimeRequirement {
119    pub name: String,
120    pub description: Option<String>,
121    pub required: bool,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
125#[serde(transparent)]
126pub struct OperationId(pub String);
127
128impl OperationId {
129    pub fn new(value: impl Into<String>) -> Self {
130        Self(value.into())
131    }
132
133    pub fn as_str(&self) -> &str {
134        &self.0
135    }
136}
137
138impl From<&str> for OperationId {
139    fn from(value: &str) -> Self {
140        Self(value.to_string())
141    }
142}
143
144impl From<String> for OperationId {
145    fn from(value: String) -> Self {
146        Self(value)
147    }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
151#[serde(rename_all = "camelCase")]
152pub struct OperationMetadata {
153    pub id: OperationId,
154    pub name: String,
155    pub description: Option<String>,
156    pub version: String,
157    pub capabilities: RuntimeCapabilities,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
161#[serde(rename_all = "camelCase")]
162pub struct PackageSurface {
163    pub library: String,
164    pub version: String,
165    pub operations: Vec<SurfaceOperation>,
166    pub capabilities: RuntimeCapabilities,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170#[serde(rename_all = "camelCase")]
171pub struct SurfaceOperation {
172    pub id: OperationId,
173    pub name: String,
174    pub description: Option<String>,
175    pub input_schema: serde_json::Value,
176    pub output_schema: serde_json::Value,
177    pub example_request: serde_json::Value,
178    pub wasm_supported: bool,
179    pub server_supported: bool,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
183#[serde(rename_all = "camelCase")]
184pub enum SurfaceExecutionMode {
185    InMemory,
186    PlannedJob,
187    BackgroundJob,
188    ExternalCommand,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
192#[serde(rename_all = "camelCase")]
193pub enum SurfaceSideEffect {
194    None,
195    ReadsFiles,
196    WritesFiles,
197    Network,
198    ExternalProcess,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
202#[serde(rename_all = "camelCase")]
203pub struct SurfaceArtifactExpectation {
204    pub id: String,
205    pub kind: String,
206    pub media_type: String,
207    pub required: bool,
208    pub description: Option<String>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
212#[serde(rename_all = "camelCase")]
213pub struct SurfaceExecutionPlan {
214    pub operation: OperationId,
215    pub mode: SurfaceExecutionMode,
216    pub side_effects: Vec<SurfaceSideEffect>,
217    pub cancellable: bool,
218    pub progress_unit: Option<String>,
219    pub expected_artifacts: Vec<SurfaceArtifactExpectation>,
220    pub requirements: Vec<RuntimeRequirement>,
221    pub max_recommended_input_bytes: Option<u64>,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
225#[serde(rename_all = "camelCase")]
226pub struct SurfaceRequest {
227    pub operation: OperationId,
228    pub input: serde_json::Value,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
232#[serde(rename_all = "camelCase")]
233pub struct SurfaceResponse {
234    pub operation: OperationId,
235    pub value: serde_json::Value,
236    pub diagnostics: Vec<Diagnostic>,
237    pub artifacts: Vec<serde_json::Value>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
241#[serde(rename_all = "camelCase")]
242pub struct SurfaceError {
243    pub code: String,
244    pub message: String,
245    pub operation: Option<OperationId>,
246    pub details: serde_json::Value,
247}
248
249impl SurfaceError {
250    pub fn invalid_request(
251        operation: Option<impl Into<OperationId>>,
252        message: impl Into<String>,
253    ) -> Self {
254        Self::new("invalid_request", operation, message, serde_json::json!({}))
255    }
256
257    pub fn unsupported_operation(
258        operation: impl Into<OperationId>,
259        package: impl Into<String>,
260    ) -> Self {
261        let operation = operation.into();
262        let package = package.into();
263        Self::new(
264            "unsupported_operation",
265            Some(operation.clone()),
266            format!(
267                "unsupported operation `{}` for {}",
268                operation.as_str(),
269                package
270            ),
271            serde_json::json!({"package": package}),
272        )
273    }
274
275    pub fn unsupported_value(
276        operation: Option<impl Into<OperationId>>,
277        field: impl Into<String>,
278        value: impl Into<String>,
279        allowed: &[&str],
280    ) -> Self {
281        let field = field.into();
282        let value = value.into();
283        Self::new(
284            "unsupported_value",
285            operation,
286            format!("unsupported value `{value}` for `{field}`"),
287            serde_json::json!({
288                "field": field,
289                "value": value,
290                "allowed": allowed
291            }),
292        )
293    }
294
295    pub fn resource_limit(
296        operation: Option<impl Into<OperationId>>,
297        field: impl Into<String>,
298        limit: usize,
299        actual: usize,
300    ) -> Self {
301        let field = field.into();
302        Self::new(
303            "resource_limit",
304            operation,
305            format!("`{field}` exceeds the maximum supported size of {limit}"),
306            serde_json::json!({
307                "field": field,
308                "limit": limit,
309                "actual": actual
310            }),
311        )
312    }
313
314    pub fn cancelled(operation: impl Into<OperationId>, message: impl Into<String>) -> Self {
315        Self::new("cancelled", Some(operation), message, serde_json::json!({}))
316    }
317
318    pub fn execution_failed(
319        operation: impl Into<OperationId>,
320        message: impl Into<String>,
321        details: serde_json::Value,
322    ) -> Self {
323        Self::new("execution_failed", Some(operation), message, details)
324    }
325
326    pub fn artifact_error(
327        operation: impl Into<OperationId>,
328        message: impl Into<String>,
329        details: serde_json::Value,
330    ) -> Self {
331        Self::new("artifact_error", Some(operation), message, details)
332    }
333
334    pub fn missing_dependency(
335        operation: Option<impl Into<OperationId>>,
336        dependency: impl Into<String>,
337        setup: impl Into<String>,
338    ) -> Self {
339        let dependency = dependency.into();
340        let setup = setup.into();
341        Self::new(
342            "missing_dependency",
343            operation,
344            format!("missing required dependency `{dependency}`"),
345            serde_json::json!({
346                "dependency": dependency,
347                "setup": setup
348            }),
349        )
350    }
351
352    pub fn new(
353        code: impl Into<String>,
354        operation: Option<impl Into<OperationId>>,
355        message: impl Into<String>,
356        details: serde_json::Value,
357    ) -> Self {
358        Self {
359            code: code.into(),
360            message: message.into(),
361            operation: operation.map(Into::into),
362            details,
363        }
364    }
365
366    pub fn to_error_string(&self) -> String {
367        serde_json::to_string(self).unwrap_or_else(|_| self.message.clone())
368    }
369}
370
371impl fmt::Display for SurfaceError {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        f.write_str(&self.message)
374    }
375}
376
377impl std::error::Error for SurfaceError {}
378
379pub fn parse_surface_error(error: &str) -> Option<SurfaceError> {
380    serde_json::from_str(error).ok()
381}
382
383pub fn parse_surface_input<T: for<'de> Deserialize<'de>>(
384    operation: Option<&str>,
385    input: serde_json::Value,
386) -> Result<T, String> {
387    serde_json::from_value(input).map_err(|error| {
388        SurfaceError::invalid_request(
389            operation.map(OperationId::new),
390            format!("invalid request: {error}"),
391        )
392        .to_error_string()
393    })
394}
395
396pub fn require_non_empty<T>(operation: &str, field: &str, values: &[T]) -> Result<(), String> {
397    if values.is_empty() {
398        Err(SurfaceError::invalid_request(
399            Some(OperationId::new(operation)),
400            format!("invalid request: {field} must not be empty"),
401        )
402        .to_error_string())
403    } else {
404        Ok(())
405    }
406}
407
408pub fn validate_max_items(
409    operation: &str,
410    field: &str,
411    actual: usize,
412    limit: usize,
413) -> Result<(), String> {
414    if actual > limit {
415        Err(
416            SurfaceError::resource_limit(Some(OperationId::new(operation)), field, limit, actual)
417                .to_error_string(),
418        )
419    } else {
420        Ok(())
421    }
422}
423
424pub fn validate_matching_lengths(
425    operation: &str,
426    left_field: &str,
427    left_len: usize,
428    right_field: &str,
429    right_len: usize,
430) -> Result<(), String> {
431    if left_len != right_len {
432        Err(SurfaceError::invalid_request(
433            Some(OperationId::new(operation)),
434            format!(
435                "invalid request: `{left_field}` length {left_len} must match `{right_field}` length {right_len}"
436            ),
437        )
438        .to_error_string())
439    } else {
440        Ok(())
441    }
442}
443
444/// Builds the standard package-surface operation metadata used by library
445/// crates and transport adapters.
446pub fn surface_operation(
447    id: impl Into<String>,
448    name: impl Into<String>,
449    description: impl Into<String>,
450    example_request: serde_json::Value,
451) -> SurfaceOperation {
452    let id = id.into();
453    SurfaceOperation {
454        id: OperationId::new(id.clone()),
455        name: name.into(),
456        description: Some(description.into()),
457        input_schema: surface_input_schema(&id, &example_request),
458        output_schema: surface_output_schema(&id),
459        example_request,
460        wasm_supported: true,
461        server_supported: true,
462    }
463}
464
465pub fn surface_operation_with_execution_plan(
466    id: impl Into<String>,
467    name: impl Into<String>,
468    description: impl Into<String>,
469    example_request: serde_json::Value,
470    execution_plan: SurfaceExecutionPlan,
471) -> SurfaceOperation {
472    let mut operation = surface_operation(id, name, description, example_request);
473    let execution_plan = surface_execution_plan_value(&execution_plan);
474    insert_schema_extension(
475        &mut operation.input_schema,
476        "xExecutionPlan",
477        execution_plan.clone(),
478    );
479    insert_schema_extension(
480        &mut operation.output_schema,
481        "xExecutionPlan",
482        execution_plan,
483    );
484    operation
485}
486
487pub fn surface_execution_plan_value(plan: &SurfaceExecutionPlan) -> serde_json::Value {
488    serde_json::to_value(plan).unwrap_or_else(|_| serde_json::json!({}))
489}
490
491fn insert_schema_extension(schema: &mut serde_json::Value, key: &str, value: serde_json::Value) {
492    if let serde_json::Value::Object(object) = schema {
493        object.insert(key.to_string(), value);
494    }
495}
496
497pub fn surface_input_schema(
498    operation: &str,
499    example_request: &serde_json::Value,
500) -> serde_json::Value {
501    let properties = match example_request {
502        serde_json::Value::Object(object) => object
503            .iter()
504            .map(|(key, value)| (key.clone(), infer_schema_for_value(key, value)))
505            .collect::<serde_json::Map<_, _>>(),
506        _ => serde_json::Map::new(),
507    };
508    let required = required_fields_for_operation(operation, example_request);
509    serde_json::json!({
510        "type": "object",
511        "additionalProperties": false,
512        "properties": properties,
513        "required": required,
514        "xOperationCategory": operation_category(operation),
515        "xReleaseStability": "stable",
516        "xContractPolicy": "additiveOnly",
517        "xErrorShape": {
518            "code": "string",
519            "message": "string",
520            "operation": "string|null",
521            "details": "object"
522        },
523        "xResourceLimits": {
524            "maxRecommendedInputBytes": 1048576,
525            "largePayloadBehavior": "reject or deterministically truncate by operation-specific limit"
526        }
527    })
528}
529
530pub fn surface_output_schema(operation: &str) -> serde_json::Value {
531    serde_json::json!({
532        "type": "object",
533        "required": ["operation", "title", "message", "summary", "result"],
534        "properties": {
535            "operation": {"type": "string", "const": operation},
536            "title": {"type": "string", "minLength": 1},
537            "message": {"type": "string", "minLength": 1},
538            "summary": {"type": "object"},
539            "result": {}
540        },
541        "additionalProperties": true
542    })
543}
544
545pub fn operation_category(operation: &str) -> &'static str {
546    match operation {
547        "describe"
548        | "analysis.describe"
549        | "classification.models"
550        | "classification.schema"
551        | "embeddings.backends"
552        | "qa.models" => "debug",
553        "runtime.softmax" => "support",
554        _ => "workflow",
555    }
556}
557
558fn required_fields_for_operation(
559    operation: &str,
560    example_request: &serde_json::Value,
561) -> Vec<String> {
562    if operation == "describe" || operation.ends_with(".models") || operation.ends_with(".describe")
563    {
564        return Vec::new();
565    }
566    let optional = [
567        "dimensions",
568        "embedding",
569        "id",
570        "includeNearDuplicates",
571        "includePunctuation",
572        "includeSemanticNeighbors",
573        "keywordLimit",
574        "linguistics",
575        "lowercase",
576        "maxAlternatives",
577        "maxTokens",
578        "minTokensForDecision",
579        "mode",
580        "model",
581        "n",
582        "ngramSizes",
583        "normalizeWhitespace",
584        "options",
585        "order",
586        "profile",
587        "previewLimit",
588        "seed",
589        "sentenceLevel",
590        "shingleSizes",
591        "streamId",
592        "summarySentences",
593        "topK",
594        "truncation",
595    ];
596    match example_request {
597        serde_json::Value::Object(object) => object
598            .keys()
599            .filter(|key| !optional.contains(&key.as_str()))
600            .cloned()
601            .collect(),
602        _ => Vec::new(),
603    }
604}
605
606fn infer_schema_for_value(key: &str, value: &serde_json::Value) -> serde_json::Value {
607    let mut schema = match value {
608        serde_json::Value::Bool(_) => serde_json::json!({"type": "boolean"}),
609        serde_json::Value::Number(number) if number.is_i64() || number.is_u64() => {
610            serde_json::json!({"type": "integer", "minimum": 0})
611        }
612        serde_json::Value::Number(_) => serde_json::json!({"type": "number"}),
613        serde_json::Value::String(_) => serde_json::json!({"type": "string", "minLength": 1}),
614        serde_json::Value::Array(values) => {
615            let item_schema = values
616                .first()
617                .map(|value| infer_schema_for_value("item", value))
618                .unwrap_or_else(|| serde_json::json!({}));
619            serde_json::json!({"type": "array", "items": item_schema, "minItems": 1})
620        }
621        serde_json::Value::Object(object) => serde_json::json!({
622            "type": "object",
623            "additionalProperties": true,
624            "properties": object
625                .iter()
626                .map(|(key, value)| (key.clone(), infer_schema_for_value(key, value)))
627                .collect::<serde_json::Map<_, _>>()
628        }),
629        serde_json::Value::Null => serde_json::json!({}),
630    };
631    if matches!(
632        key,
633        "topK" | "top_k" | "maxTokens" | "max_tokens" | "order" | "dimensions" | "n"
634    ) {
635        if let serde_json::Value::Object(object) = &mut schema {
636            object.insert("minimum".to_string(), serde_json::json!(1));
637            object.insert("maximum".to_string(), serde_json::json!(4096));
638        }
639    }
640    schema
641}
642
643/// Builds the standard `describe` response without changing the shared
644/// `SurfaceResponse` JSON shape.
645pub fn describe_surface_response(
646    surface: &PackageSurface,
647    request: SurfaceRequest,
648) -> SurfaceResponse {
649    let result = serde_json::json!({
650        "library": &surface.library,
651        "version": &surface.version,
652        "operationCount": surface.operations.len(),
653        "operations": surface
654            .operations
655            .iter()
656            .map(|operation| operation.id.as_str())
657            .collect::<Vec<_>>(),
658        "input": request.input
659    });
660    structured_surface_response(
661        request.operation,
662        "Package surface metadata",
663        format!(
664            "{} exposes {} package-surface operations.",
665            surface.library,
666            surface.operations.len()
667        ),
668        serde_json::json!({
669            "operationCount": surface.operations.len(),
670            "runtime": {
671                "wasm": surface.capabilities.wasm,
672                "server": surface.capabilities.server,
673                "native": surface.capabilities.native
674            }
675        }),
676        result,
677    )
678}
679
680/// Builds a successful surface response with empty diagnostics and artifacts.
681pub fn surface_response(operation: OperationId, value: serde_json::Value) -> SurfaceResponse {
682    let title = operation.as_str().to_string();
683    let message = format!("Ran package-surface operation `{}`.", operation.as_str());
684    let value = ensure_structured_surface_value(&operation, title, message, value);
685    SurfaceResponse {
686        operation,
687        value,
688        diagnostics: Vec::new(),
689        artifacts: Vec::new(),
690    }
691}
692
693/// Builds a package-surface value with standard human-readable metadata while
694/// preserving object fields from the concrete operation result at the top level.
695pub fn structured_surface_value(
696    operation: &OperationId,
697    title: impl Into<String>,
698    message: impl Into<String>,
699    summary: serde_json::Value,
700    result: serde_json::Value,
701) -> serde_json::Value {
702    let mut object = match &result {
703        serde_json::Value::Object(map) => map.clone(),
704        _ => serde_json::Map::new(),
705    };
706    object.insert("title".to_string(), serde_json::Value::String(title.into()));
707    object.insert(
708        "operation".to_string(),
709        serde_json::Value::String(operation.as_str().to_string()),
710    );
711    object.insert(
712        "message".to_string(),
713        serde_json::Value::String(message.into()),
714    );
715    object.insert("summary".to_string(), summary);
716    object.insert("result".to_string(), result);
717    serde_json::Value::Object(object)
718}
719
720/// Adds the common package-surface UI fields to a result value when they are
721/// missing, while preserving every existing top-level domain field.
722pub fn ensure_structured_surface_value(
723    operation: &OperationId,
724    title: impl Into<String>,
725    message: impl Into<String>,
726    value: serde_json::Value,
727) -> serde_json::Value {
728    let result = value.clone();
729    let mut object = match value {
730        serde_json::Value::Object(map) => map,
731        _ => serde_json::Map::new(),
732    };
733    object
734        .entry("operation".to_string())
735        .or_insert_with(|| serde_json::Value::String(operation.as_str().to_string()));
736    object
737        .entry("title".to_string())
738        .or_insert_with(|| serde_json::Value::String(title.into()));
739    object
740        .entry("message".to_string())
741        .or_insert_with(|| serde_json::Value::String(message.into()));
742    object
743        .entry("summary".to_string())
744        .or_insert_with(|| operation_summary(&result));
745    object.entry("result".to_string()).or_insert(result);
746    serde_json::Value::Object(object)
747}
748
749/// Builds a successful package-surface response using `structured_surface_value`.
750pub fn structured_surface_response(
751    operation: OperationId,
752    title: impl Into<String>,
753    message: impl Into<String>,
754    summary: serde_json::Value,
755    result: serde_json::Value,
756) -> SurfaceResponse {
757    let value = structured_surface_value(&operation, title, message, summary, result);
758    surface_response(operation, value)
759}
760
761/// Builds a structured response for an operation listed in a package surface.
762///
763/// This keeps the concrete operation result at the top level for compatibility,
764/// while adding the common `title`, `message`, `summary`, and `result` fields
765/// expected by package-surface UIs.
766pub fn structured_operation_response(
767    surface: &PackageSurface,
768    operation: OperationId,
769    result: serde_json::Value,
770) -> SurfaceResponse {
771    let metadata = surface
772        .operations
773        .iter()
774        .find(|candidate| candidate.id.as_str() == operation.as_str());
775    let title = metadata
776        .map(|operation| operation.name.clone())
777        .unwrap_or_else(|| operation.as_str().to_string());
778    let message = metadata
779        .and_then(|operation| operation.description.clone())
780        .unwrap_or_else(|| format!("Ran package-surface operation `{}`.", operation.as_str()));
781    let summary = operation_summary(&result);
782    structured_surface_response(operation, title, message, summary, result)
783}
784
785fn operation_summary(result: &serde_json::Value) -> serde_json::Value {
786    match result {
787        serde_json::Value::Object(object) => {
788            let mut summary = serde_json::Map::new();
789            summary.insert("status".to_string(), serde_json::json!("ok"));
790            for key in [
791                "count",
792                "width",
793                "height",
794                "format",
795                "pixelFormat",
796                "dimensions",
797                "operationCount",
798            ] {
799                if let Some(value) = object.get(key) {
800                    summary.insert(key.to_string(), value.clone());
801                }
802            }
803            if let Some((key, value)) = object
804                .iter()
805                .find(|(_, value)| matches!(value, serde_json::Value::Array(_)))
806            {
807                summary.insert(
808                    format!("{key}Count"),
809                    serde_json::json!(value.as_array().map(Vec::len).unwrap_or(0)),
810                );
811            }
812            serde_json::Value::Object(summary)
813        }
814        serde_json::Value::Array(values) => {
815            serde_json::json!({"status": "ok", "count": values.len()})
816        }
817        _ => serde_json::json!({"status": "ok"}),
818    }
819}
820
821/// Shared helpers for thin package-surface CLI adapters.
822pub mod cli {
823    use std::fs;
824    use std::io::{self, Read};
825
826    use super::{
827        ensure_structured_surface_value, OperationId, PackageSurface, SurfaceRequest,
828        SurfaceResponse,
829    };
830
831    /// Static package metadata for one CLI adapter.
832    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
833    pub struct CliAdapterMetadata {
834        pub library_crate: &'static str,
835        pub surface_kind: &'static str,
836        pub library_import: &'static str,
837        pub server_package: &'static str,
838        pub app_package: &'static str,
839        pub wasm_package: &'static str,
840    }
841
842    /// Builds the standard CLI adapter metadata payload.
843    pub fn package_metadata_json(metadata: CliAdapterMetadata, surface: PackageSurface) -> String {
844        serde_json::json!({
845            "package": format!("{}-cli", metadata.library_crate),
846            "surface": metadata.surface_kind,
847            "library": metadata.library_crate,
848            "libraryImport": metadata.library_import,
849            "serverPackage": metadata.server_package,
850            "appPackage": metadata.app_package,
851            "wasmPackage": metadata.wasm_package,
852            "operations": surface.operations
853        })
854        .to_string()
855    }
856
857    /// Builds the standard CLI command schema payload.
858    pub fn command_schema_json() -> String {
859        serde_json::json!({
860            "commands": [
861                {"name": "info", "description": "Print package and adapter metadata."},
862                {"name": "schema", "description": "Print the CLI command schema."},
863                {"name": "operations", "description": "Print library operations."},
864                {"name": "run", "description": "Run one library-owned operation."}
865            ]
866        })
867        .to_string()
868    }
869
870    /// Reads a JSON request from `--json`, `--file`, or stdin.
871    pub fn read_json_input(
872        json: Option<String>,
873        file: Option<String>,
874    ) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
875        let input = if let Some(json) = json {
876            json
877        } else if let Some(file) = file {
878            fs::read_to_string(file)?
879        } else {
880            let mut buffer = String::new();
881            io::stdin().read_to_string(&mut buffer)?;
882            if buffer.trim().is_empty() {
883                "{}".to_string()
884            } else {
885                buffer
886            }
887        };
888        Ok(serde_json::from_str(&input)?)
889    }
890
891    /// Runs an operation through a library-owned surface and adds standard
892    /// package-surface value fields if an older surface omitted them.
893    pub fn run_wrapped_operation(
894        operation: &str,
895        input: serde_json::Value,
896        runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
897    ) -> Result<SurfaceResponse, String> {
898        let mut response = runner(SurfaceRequest {
899            operation: OperationId::new(operation),
900            input,
901        })?;
902        let value = std::mem::take(&mut response.value);
903        response.value = ensure_structured_surface_value(
904            &response.operation,
905            operation.to_string(),
906            format!("Ran package-surface operation `{}`.", operation),
907            value,
908        );
909        Ok(response)
910    }
911}
912
913/// Shared helpers for local package-surface HTTP adapters.
914pub mod server {
915    use std::io::{self, BufRead, BufReader, Read, Write};
916    use std::net::{TcpListener, TcpStream};
917
918    use super::{
919        parse_surface_error, Diagnostic, DiagnosticSeverity, OperationId, PackageSurface,
920        SurfaceRequest, SurfaceResponse,
921    };
922
923    /// Static package metadata for one server adapter.
924    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
925    pub struct ServerAdapterMetadata {
926        pub library_crate: &'static str,
927        pub surface_kind: &'static str,
928        pub library_import: &'static str,
929        pub cli_package: &'static str,
930        pub app_package: &'static str,
931        pub wasm_package: &'static str,
932    }
933
934    #[derive(Debug, Clone, PartialEq, Eq)]
935    pub struct HttpResponse {
936        pub status_code: u16,
937        pub reason: &'static str,
938        pub content_type: &'static str,
939        pub body: String,
940    }
941
942    /// Serves the standard local package-surface HTTP API.
943    pub fn serve(
944        addr: &str,
945        metadata: ServerAdapterMetadata,
946        surface_provider: fn() -> PackageSurface,
947        runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
948    ) -> io::Result<()> {
949        let listener = TcpListener::bind(addr)?;
950        for stream in listener.incoming() {
951            handle_stream(stream?, metadata, surface_provider, runner)?;
952        }
953        Ok(())
954    }
955
956    /// Returns the standard response for one HTTP request.
957    pub fn response_for(
958        method: &str,
959        path: &str,
960        body: &str,
961        metadata: ServerAdapterMetadata,
962        surface_provider: fn() -> PackageSurface,
963        runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
964    ) -> HttpResponse {
965        match (method, path) {
966            ("OPTIONS", _) => HttpResponse {
967                status_code: 204,
968                reason: "No Content",
969                content_type: "application/json",
970                body: String::new(),
971            },
972            ("GET", "/health") => json_response(
973                200,
974                "OK",
975                serde_json::json!({
976                    "ok": true,
977                    "package": format!("{}-server", metadata.library_crate),
978                    "library": metadata.library_crate
979                }),
980            ),
981            ("GET", "/api/package") => json_response(
982                200,
983                "OK",
984                package_metadata_value(metadata, surface_provider()),
985            ),
986            ("GET", "/api/schema") => {
987                json_response(200, "OK", schema_value(metadata, surface_provider()))
988            }
989            ("GET", "/api/operations") => {
990                json_response(200, "OK", serde_json::json!(surface_provider().operations))
991            }
992            ("POST", "/api/run") => run_response(body, metadata, runner),
993            ("POST", path) if path.starts_with("/api/") => {
994                let operation = path.trim_start_matches("/api/");
995                run_request(
996                    SurfaceRequest {
997                        operation: OperationId::new(operation),
998                        input: parse_json_or_empty(body),
999                    },
1000                    metadata,
1001                    runner,
1002                )
1003            }
1004            _ => json_response(
1005                404,
1006                "Not Found",
1007                serde_json::json!({
1008                    "error": "not found",
1009                    "path": path
1010                }),
1011            ),
1012        }
1013    }
1014
1015    /// Builds the standard server adapter metadata payload.
1016    pub fn package_metadata_json(
1017        metadata: ServerAdapterMetadata,
1018        surface: PackageSurface,
1019    ) -> String {
1020        package_metadata_value(metadata, surface).to_string()
1021    }
1022
1023    fn package_metadata_value(
1024        metadata: ServerAdapterMetadata,
1025        surface: PackageSurface,
1026    ) -> serde_json::Value {
1027        serde_json::json!({
1028            "package": format!("{}-server", metadata.library_crate),
1029            "surface": metadata.surface_kind,
1030            "library": metadata.library_crate,
1031            "libraryImport": metadata.library_import,
1032            "cliPackage": metadata.cli_package,
1033            "appPackage": metadata.app_package,
1034            "wasmPackage": metadata.wasm_package,
1035            "endpoints": [
1036                "GET /health",
1037                "GET /api/package",
1038                "GET /api/schema",
1039                "GET /api/operations",
1040                "POST /api/run",
1041                "POST /api/<operation-id>"
1042            ],
1043            "runtimeMetadata": {
1044                "candleDevice": serde_json::Value::Null
1045            },
1046            "operations": surface.operations
1047        })
1048    }
1049
1050    fn schema_value(metadata: ServerAdapterMetadata, surface: PackageSurface) -> serde_json::Value {
1051        let operations = surface
1052            .operations
1053            .into_iter()
1054            .map(|operation| {
1055                let path = format!("/api/{}", operation.id.as_str());
1056                (
1057                    path,
1058                    serde_json::json!({
1059                        "post": {
1060                            "summary": operation.name,
1061                            "description": operation.description,
1062                            "requestBody": operation.input_schema,
1063                            "responses": {"200": operation.output_schema}
1064                        }
1065                    }),
1066                )
1067            })
1068            .collect::<serde_json::Map<_, _>>();
1069
1070        serde_json::json!({
1071            "openapi": "3.1.0",
1072            "info": {
1073                "title": format!("{} API", metadata.library_crate),
1074                "version": env!("CARGO_PKG_VERSION")
1075            },
1076            "paths": operations
1077        })
1078    }
1079
1080    fn run_response(
1081        body: &str,
1082        metadata: ServerAdapterMetadata,
1083        runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
1084    ) -> HttpResponse {
1085        let payload = match serde_json::from_str::<serde_json::Value>(body) {
1086            Ok(value) => value,
1087            Err(error) => {
1088                return diagnostic_response(
1089                    400,
1090                    "Bad Request",
1091                    "invalid_request",
1092                    &format!("invalid JSON: {error}"),
1093                    metadata,
1094                );
1095            }
1096        };
1097        let operation = payload
1098            .get("operation")
1099            .and_then(serde_json::Value::as_str)
1100            .unwrap_or("describe")
1101            .to_string();
1102        let input = payload
1103            .get("input")
1104            .cloned()
1105            .unwrap_or_else(|| payload.clone());
1106        run_request(
1107            SurfaceRequest {
1108                operation: OperationId::new(operation),
1109                input,
1110            },
1111            metadata,
1112            runner,
1113        )
1114    }
1115
1116    fn run_request(
1117        request: SurfaceRequest,
1118        metadata: ServerAdapterMetadata,
1119        runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
1120    ) -> HttpResponse {
1121        match runner(request) {
1122            Ok(response) => json_response(200, "OK", serde_json::json!(response)),
1123            Err(error) => {
1124                diagnostic_response(400, "Bad Request", "operation_failed", &error, metadata)
1125            }
1126        }
1127    }
1128
1129    fn diagnostic_response(
1130        status_code: u16,
1131        reason: &'static str,
1132        code: &str,
1133        message: &str,
1134        metadata: ServerAdapterMetadata,
1135    ) -> HttpResponse {
1136        let parsed = parse_surface_error(message);
1137        let diagnostic_code = parsed
1138            .as_ref()
1139            .map(|error| error.code.as_str())
1140            .unwrap_or(code);
1141        let diagnostic_message = parsed
1142            .as_ref()
1143            .map(|error| error.message.as_str())
1144            .unwrap_or(message);
1145        let details = parsed
1146            .as_ref()
1147            .map(|error| error.details.clone())
1148            .unwrap_or_else(|| serde_json::json!({}));
1149        json_response(
1150            status_code,
1151            reason,
1152            serde_json::json!({
1153                "diagnostics": [Diagnostic {
1154                    severity: DiagnosticSeverity::Error,
1155                    code: diagnostic_code.into(),
1156                    message: diagnostic_message.to_string(),
1157                    source: Some(format!("{}-server", metadata.library_crate)),
1158                    help: None,
1159                }],
1160                "error": {
1161                    "code": diagnostic_code,
1162                    "message": diagnostic_message,
1163                    "details": details
1164                }
1165            }),
1166        )
1167    }
1168
1169    fn parse_json_or_empty(body: &str) -> serde_json::Value {
1170        if body.trim().is_empty() {
1171            serde_json::json!({})
1172        } else {
1173            serde_json::from_str(body).unwrap_or_else(|_| serde_json::json!({"raw": body}))
1174        }
1175    }
1176
1177    fn handle_stream(
1178        mut stream: TcpStream,
1179        metadata: ServerAdapterMetadata,
1180        surface_provider: fn() -> PackageSurface,
1181        runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
1182    ) -> io::Result<()> {
1183        let mut reader = BufReader::new(stream.try_clone()?);
1184        let mut request_line = String::new();
1185        reader.read_line(&mut request_line)?;
1186
1187        let mut content_length = 0usize;
1188        loop {
1189            let mut header = String::new();
1190            reader.read_line(&mut header)?;
1191            let trimmed = header.trim_end();
1192            if trimmed.is_empty() {
1193                break;
1194            }
1195            if let Some((name, value)) = trimmed.split_once(':') {
1196                if name.eq_ignore_ascii_case("content-length") {
1197                    content_length = value.trim().parse().unwrap_or(0);
1198                }
1199            }
1200        }
1201
1202        let mut body = vec![0; content_length];
1203        if content_length > 0 {
1204            reader.read_exact(&mut body)?;
1205        }
1206        let body = String::from_utf8_lossy(&body);
1207
1208        let mut parts = request_line.split_whitespace();
1209        let method = parts.next().unwrap_or("GET");
1210        let path = parts.next().unwrap_or("/");
1211        let response = response_for(method, path, &body, metadata, surface_provider, runner);
1212        write_response(&mut stream, response)
1213    }
1214
1215    fn json_response(
1216        status_code: u16,
1217        reason: &'static str,
1218        value: serde_json::Value,
1219    ) -> HttpResponse {
1220        HttpResponse {
1221            status_code,
1222            reason,
1223            content_type: "application/json",
1224            body: value.to_string(),
1225        }
1226    }
1227
1228    fn write_response(stream: &mut TcpStream, response: HttpResponse) -> io::Result<()> {
1229        write!(
1230            stream,
1231            "HTTP/1.1 {} {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: content-type\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nConnection: close\r\n\r\n{}",
1232            response.status_code,
1233            response.reason,
1234            response.content_type,
1235            response.body.len(),
1236            response.body
1237        )
1238    }
1239}
1240
1241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
1242#[serde(transparent)]
1243pub struct JobId(pub String);
1244
1245impl JobId {
1246    pub fn new(value: impl Into<String>) -> Self {
1247        Self(value.into())
1248    }
1249
1250    pub fn as_str(&self) -> &str {
1251        &self.0
1252    }
1253}
1254
1255impl From<&str> for JobId {
1256    fn from(value: &str) -> Self {
1257        Self(value.to_string())
1258    }
1259}
1260
1261impl From<String> for JobId {
1262    fn from(value: String) -> Self {
1263        Self(value)
1264    }
1265}
1266
1267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
1268#[serde(transparent)]
1269pub struct ArtifactId(pub String);
1270
1271impl ArtifactId {
1272    pub fn new(value: impl Into<String>) -> Self {
1273        Self(value.into())
1274    }
1275
1276    pub fn as_str(&self) -> &str {
1277        &self.0
1278    }
1279}
1280
1281impl From<&str> for ArtifactId {
1282    fn from(value: &str) -> Self {
1283        Self(value.to_string())
1284    }
1285}
1286
1287impl From<String> for ArtifactId {
1288    fn from(value: String) -> Self {
1289        Self(value)
1290    }
1291}
1292
1293#[cfg(test)]
1294mod tests {
1295    use super::*;
1296
1297    #[test]
1298    fn diagnostic_uses_camel_case_json() {
1299        let diagnostic = Diagnostic::new(DiagnosticSeverity::Warning, "demo.warning", "check");
1300        let json = serde_json::to_string(&diagnostic).expect("serialize diagnostic");
1301
1302        assert!(json.contains("\"severity\":\"warning\""));
1303        assert!(json.contains("\"code\":\"demo.warning\""));
1304    }
1305
1306    #[test]
1307    fn pure_rust_capabilities_allow_wasm_and_server() {
1308        let capabilities = RuntimeCapabilities::pure_rust();
1309
1310        assert!(capabilities.native);
1311        assert!(capabilities.server);
1312        assert!(capabilities.wasm);
1313        assert_eq!(capabilities.mobile, MobileCapability::Wasm);
1314    }
1315
1316    #[test]
1317    fn capability_builders_preserve_pure_rust_defaults() {
1318        let capabilities = RuntimeCapabilities::pure_rust()
1319            .with_max_recommended_input_bytes(1024)
1320            .with_requirement("fixture", "test fixture input", false);
1321
1322        assert!(capabilities.native);
1323        assert!(capabilities.server);
1324        assert!(capabilities.wasm);
1325        assert_eq!(capabilities.max_recommended_input_bytes, Some(1024));
1326        assert_eq!(capabilities.requirements[0].name, "fixture");
1327        assert!(!capabilities.requirements[0].required);
1328    }
1329
1330    #[test]
1331    fn package_surface_uses_camel_case_json() {
1332        let surface = PackageSurface {
1333            library: "demo-core".to_string(),
1334            version: "0.1.0".to_string(),
1335            capabilities: RuntimeCapabilities::pure_rust(),
1336            operations: vec![SurfaceOperation {
1337                id: OperationId::new("describe"),
1338                name: "Describe".to_string(),
1339                description: Some("Describe package surface".to_string()),
1340                input_schema: serde_json::json!({"type": "object"}),
1341                output_schema: serde_json::json!({"type": "object"}),
1342                example_request: serde_json::json!({}),
1343                wasm_supported: true,
1344                server_supported: true,
1345            }],
1346        };
1347
1348        let json = serde_json::to_string(&surface).expect("serialize surface");
1349
1350        assert!(json.contains("\"inputSchema\""));
1351        assert!(json.contains("\"exampleRequest\""));
1352        assert!(json.contains("\"wasmSupported\":true"));
1353    }
1354
1355    #[test]
1356    fn surface_helpers_preserve_standard_response_shape() {
1357        let surface = PackageSurface {
1358            library: "demo".to_string(),
1359            version: "0.1.0".to_string(),
1360            capabilities: RuntimeCapabilities::pure_rust(),
1361            operations: vec![surface_operation(
1362                "describe",
1363                "Describe",
1364                "Describe demo package",
1365                serde_json::json!({"includeOperations": true}),
1366            )],
1367        };
1368        let response = describe_surface_response(
1369            &surface,
1370            SurfaceRequest {
1371                operation: OperationId::new("describe"),
1372                input: serde_json::json!({"includeOperations": true}),
1373            },
1374        );
1375
1376        assert_eq!(response.operation.as_str(), "describe");
1377        assert_eq!(response.value["library"], "demo");
1378        assert_eq!(response.value["operationCount"], 1);
1379        assert_eq!(response.diagnostics, Vec::new());
1380        assert_eq!(response.artifacts, Vec::<serde_json::Value>::new());
1381    }
1382
1383    #[test]
1384    fn surface_operation_declares_release_contract_schema() {
1385        let operation = surface_operation(
1386            "demo.run",
1387            "Run demo",
1388            "Run a demo workflow",
1389            serde_json::json!({"text": "hello", "topK": 3}),
1390        );
1391
1392        assert_eq!(operation.input_schema["additionalProperties"], false);
1393        assert_eq!(operation.input_schema["xOperationCategory"], "workflow");
1394        assert_eq!(operation.input_schema["xReleaseStability"], "stable");
1395        assert_eq!(
1396            operation.input_schema["required"],
1397            serde_json::json!(["text"])
1398        );
1399        assert_eq!(operation.input_schema["properties"]["topK"]["minimum"], 1);
1400        assert_eq!(operation.output_schema["required"][0], "operation");
1401    }
1402
1403    #[test]
1404    fn typed_surface_errors_roundtrip_for_transport_adapters() {
1405        let error = SurfaceError::unsupported_operation("demo.missing", "demo-package");
1406        let serialized = error.to_error_string();
1407        let parsed = parse_surface_error(&serialized).expect("typed surface error");
1408
1409        assert_eq!(parsed.code, "unsupported_operation");
1410        assert_eq!(parsed.operation.unwrap().as_str(), "demo.missing");
1411        assert!(parsed.message.contains("unsupported operation"));
1412    }
1413
1414    #[test]
1415    fn surface_error_is_standard_error_type() {
1416        let error = SurfaceError::invalid_request(Some("demo.run"), "invalid request: missing id");
1417        let boxed: Box<dyn std::error::Error> = Box::new(error.clone());
1418
1419        assert_eq!(boxed.to_string(), "invalid request: missing id");
1420        assert_eq!(error.to_string(), "invalid request: missing id");
1421    }
1422
1423    #[test]
1424    fn validation_helpers_return_typed_errors() {
1425        let limit = validate_max_items("demo.run", "values", 3, 2).expect_err("limit error");
1426        let parsed = parse_surface_error(&limit).expect("typed resource error");
1427        assert_eq!(parsed.code, "resource_limit");
1428        assert_eq!(parsed.details["field"], "values");
1429        assert_eq!(parsed.details["actual"], 3);
1430
1431        let length =
1432            validate_matching_lengths("demo.run", "left", 2, "right", 3).expect_err("length");
1433        let parsed = parse_surface_error(&length).expect("typed length error");
1434        assert_eq!(parsed.code, "invalid_request");
1435        assert!(parsed.message.contains("left"));
1436        assert!(parsed.message.contains("right"));
1437    }
1438
1439    #[test]
1440    fn execution_plan_serializes_to_schema_extension() {
1441        let plan = SurfaceExecutionPlan {
1442            operation: OperationId::new("demo.run"),
1443            mode: SurfaceExecutionMode::PlannedJob,
1444            side_effects: vec![SurfaceSideEffect::None],
1445            cancellable: true,
1446            progress_unit: Some("items".to_string()),
1447            expected_artifacts: vec![SurfaceArtifactExpectation {
1448                id: "report".to_string(),
1449                kind: "json".to_string(),
1450                media_type: "application/json".to_string(),
1451                required: true,
1452                description: Some("Structured report".to_string()),
1453            }],
1454            requirements: vec![RuntimeRequirement {
1455                name: "runtime-core".to_string(),
1456                description: Some("Pure Rust planner".to_string()),
1457                required: true,
1458            }],
1459            max_recommended_input_bytes: Some(1024),
1460        };
1461
1462        let operation = surface_operation_with_execution_plan(
1463            "demo.run",
1464            "Run demo",
1465            "Build a deterministic demo plan",
1466            serde_json::json!({"items": [1]}),
1467            plan,
1468        );
1469
1470        assert_eq!(
1471            operation.input_schema["xExecutionPlan"]["mode"],
1472            serde_json::json!("plannedJob")
1473        );
1474        assert_eq!(
1475            operation.output_schema["xExecutionPlan"]["expectedArtifacts"][0]["id"],
1476            serde_json::json!("report")
1477        );
1478        assert_eq!(
1479            operation.input_schema["xExecutionPlan"]["sideEffects"],
1480            serde_json::json!(["none"])
1481        );
1482    }
1483
1484    #[test]
1485    fn new_surface_error_constructors_are_typed_json() {
1486        let cancelled = SurfaceError::cancelled("demo.run", "cancelled by request");
1487        assert_eq!(cancelled.code, "cancelled");
1488        assert_eq!(cancelled.operation.unwrap().as_str(), "demo.run");
1489
1490        let execution = SurfaceError::execution_failed(
1491            "demo.run",
1492            "execution failed",
1493            serde_json::json!({
1494                "stage": "prepare"
1495            }),
1496        );
1497        assert_eq!(execution.code, "execution_failed");
1498        assert_eq!(execution.details["stage"], "prepare");
1499
1500        let artifact = SurfaceError::artifact_error(
1501            "demo.run",
1502            "artifact invalid",
1503            serde_json::json!({
1504                "artifact": "report"
1505            }),
1506        );
1507        assert_eq!(artifact.code, "artifact_error");
1508        assert_eq!(artifact.details["artifact"], "report");
1509    }
1510
1511    #[test]
1512    fn structured_operation_response_preserves_result_fields() {
1513        let surface = PackageSurface {
1514            library: "demo".to_string(),
1515            version: "0.1.0".to_string(),
1516            capabilities: RuntimeCapabilities::pure_rust(),
1517            operations: vec![surface_operation(
1518                "demo.run",
1519                "Run demo",
1520                "Run a demo workflow",
1521                serde_json::json!({"values": [1, 2]}),
1522            )],
1523        };
1524        let response = structured_operation_response(
1525            &surface,
1526            OperationId::new("demo.run"),
1527            serde_json::json!({"count": 2, "values": [1, 2]}),
1528        );
1529
1530        assert_eq!(response.value["count"], 2);
1531        assert_eq!(response.value["operation"], "demo.run");
1532        assert_eq!(response.value["title"], "Run demo");
1533        assert_eq!(response.value["summary"]["count"], 2);
1534        assert_eq!(
1535            response.value["result"]["values"],
1536            serde_json::json!([1, 2])
1537        );
1538    }
1539}