use std::collections::BTreeSet;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use serde_json::Value;
use supercode::{
ChatMessage, FrontendEvent, FrontendFacadeMethod, FrontendFacadeTransport, FrontendRequest,
FrontendResponse, FrontendRuntimeDescriptor, FrontendRuntimeError, GeneratedFrontendClient,
};
fn contract() -> Value {
serde_json::from_str(include_str!("../../../sdk/frontend/contract.json")).unwrap()
}
fn conformance_fixture() -> Value {
serde_json::from_str(include_str!(
"../../../sdk/frontend/test/fixtures/conformance.json"
))
.unwrap()
}
#[test]
fn generated_rust_catalog_is_exactly_the_language_neutral_v2_contract() {
let contract = contract();
assert_eq!(contract["schema"], "supercode.frontend.contract.v2");
assert_eq!(contract["canonical_sdk"], "supercode.sdk.v1");
let methods = contract["methods"].as_array().unwrap();
assert_eq!(methods.len(), FrontendFacadeMethod::ALL.len());
for (definition, generated) in methods.iter().zip(FrontendFacadeMethod::ALL) {
assert_eq!(definition["id"], generated.id());
assert_eq!(definition["wire"], generated.wire_name());
assert_eq!(definition["sdk_action"], generated.sdk_action());
assert_eq!(
definition["transports"],
serde_json::json!(["local", "http", "websocket", "acp"])
);
assert_eq!(
FrontendFacadeMethod::from_wire_name(generated.wire_name()),
Some(generated)
);
}
for (alias, id) in contract["compatibility_aliases"].as_object().unwrap() {
assert_eq!(
FrontendFacadeMethod::from_wire_name(alias).map(FrontendFacadeMethod::id),
id.as_str()
);
}
}
#[test]
fn contract_defines_every_wire_shape_without_language_specific_type_pointers() {
fn visit(value: &Value) {
match value {
Value::Object(object) => {
assert!(!object.contains_key("rust"));
assert!(!object.contains_key("typescript"));
for value in object.values() {
visit(value);
}
}
Value::Array(values) => values.iter().for_each(visit),
_ => {}
}
}
let contract = contract();
visit(&contract);
let types = contract["types"].as_object().unwrap();
for required in [
"ChatMessage",
"FrontendRuntimeDescriptor",
"FrontendAttachSnapshot",
"FrontendEvent",
"FrontendRequest",
"FrontendResponse",
"FrontendError",
"RuntimeLeaseSnapshot",
] {
let definition = types
.get(required)
.unwrap_or_else(|| panic!("contract omitted {required}"));
assert!(definition.get("kind").is_some(), "{required} has no shape");
}
assert_eq!(
types["FrontendRequest"]["fields"]["payload"]["type"],
"JsonValue"
);
assert_eq!(
types["FrontendRuntimeDescriptor"]["fields"]["extensions"]["type"]["kind"],
"map"
);
assert_eq!(
contract["opaque_extensions"]["events"],
"FrontendEvent.payload"
);
}
struct GeneratedClientFixture {
calls: Arc<Mutex<Vec<Value>>>,
}
impl FrontendFacadeTransport for GeneratedClientFixture {
fn call<'a>(
&'a self,
method: FrontendFacadeMethod,
params: Value,
) -> Pin<Box<dyn Future<Output = Result<Value, FrontendRuntimeError>> + Send + 'a>> {
Box::pin(async move {
self.calls.lock().unwrap().push(serde_json::json!({
"method": method.wire_name(),
"params": params,
}));
let fixture = conformance_fixture();
match method {
FrontendFacadeMethod::Describe => Ok(fixture["descriptor"].clone()),
FrontendFacadeMethod::SendInput => {
if params["prompt"] == fixture["competing_prompt"] {
Err(supercode::RuntimeSubmitError::Busy.into())
} else {
assert_eq!(params["prompt"], fixture["prompt"]);
Ok(serde_json::json!({"accepted":true}))
}
}
FrontendFacadeMethod::Attach => {
assert_eq!(params["limit"], 1000);
Ok(serde_json::json!({
"descriptor": fixture["descriptor"],
"history": fixture["history"]["messages"],
"history_cursor": fixture["history"]["cursor"],
"replay": fixture["replay"]
}))
}
FrontendFacadeMethod::Respond => {
if params["response"]["request_id"] == fixture["request"]["id"] {
Ok(serde_json::json!({"accepted":true}))
} else {
Err(FrontendRuntimeError::UnsupportedAction("respond"))
}
}
FrontendFacadeMethod::Detach => Ok(serde_json::json!({
"controller":null,"observers":[],"lease_ttl_ms":30000
})),
_ => Err(FrontendRuntimeError::UnsupportedAction(method.sdk_action())),
}
})
}
}
#[tokio::test]
async fn generated_rust_client_is_typed_and_preserves_opaque_extensions() {
let fixture = conformance_fixture();
let calls = Arc::new(Mutex::new(Vec::new()));
let client = GeneratedFrontendClient::new(GeneratedClientFixture {
calls: calls.clone(),
});
let descriptor = client.describe().await.unwrap();
assert_eq!(
serde_json::to_value(&descriptor).unwrap(),
fixture["descriptor"]
);
let attachment = client.attach(1000, Some(4)).await.unwrap();
assert_eq!(attachment.history_cursor, 4);
assert_eq!(
serde_json::to_value(&attachment.history).unwrap(),
fixture["history"]["messages"]
);
assert_eq!(
serde_json::to_value(&attachment.replay).unwrap(),
fixture["replay"]
);
assert_eq!(
serde_json::to_value(&attachment.replay[0]).unwrap()["payload"]["request"],
fixture["request"]
);
assert!(client
.send_input(fixture["prompt"].as_str().unwrap().into())
.await
.unwrap());
let response =
serde_json::from_value(fixture["semantic_actions"][3]["params"]["response"].clone())
.unwrap();
assert!(client.respond(response).await.unwrap());
client.detach().await.unwrap();
assert_eq!(
*calls.lock().unwrap(),
fixture["semantic_actions"].as_array().unwrap().clone()
);
let _ = client.attach(1000, None).await.unwrap();
assert!(
calls.lock().unwrap().last().unwrap()["params"]
.get("after_sequence")
.is_none(),
"optional Rust params must be omitted rather than serialized as null"
);
let busy = client
.send_input(fixture["competing_prompt"].as_str().unwrap().into())
.await
.unwrap_err();
let unsupported = serde_json::from_value(fixture["unsupported_response"].clone()).unwrap();
let unsupported = client.respond(unsupported).await.unwrap_err();
assert_eq!(
serde_json::json!([busy.code(), unsupported.code()]),
fixture["errors"]["observed"]
);
}
#[test]
fn facade_covers_traced_needs_without_importing_private_client_protocols() {
let contract = contract();
let families = contract["traced_needs"]["families"]
.as_array()
.unwrap()
.iter()
.map(|value| value.as_str().unwrap())
.collect::<BTreeSet<_>>();
assert_eq!(
families,
BTreeSet::from([
"capabilities",
"cursor_replay",
"history",
"identity",
"reconnect",
"requests",
"semantic_actions",
"sequenced_events",
"typed_errors",
])
);
let private = contract["traced_needs"]["excluded_private_protocols"]
.as_array()
.unwrap();
assert_eq!(private.len(), 4);
let method_bytes = serde_json::to_string(&contract["methods"]).unwrap();
for name in private {
assert!(!method_bytes.contains(name.as_str().unwrap()));
}
for forbidden_policy in [
"provider_auth",
"model_routing",
"session_format",
"reduction_policy",
"scheduler_activation",
"agent_start",
] {
assert!(
!method_bytes.contains(forbidden_policy),
"facade selection leaked runtime policy `{forbidden_policy}`"
);
}
let allowlist: Value = serde_json::from_str(include_str!(
"../../../scripts/client-protocol-corpus/allowlist.json"
))
.unwrap();
assert_eq!(
allowlist["schema"],
"supercode.stock-client-required-methods.v1"
);
assert_eq!(
contract["traced_needs"]["source"],
"scripts/client-protocol-corpus/allowlist.json"
);
}
#[test]
fn shared_binding_fixture_names_ordered_actions_events_and_errors() {
let fixture = conformance_fixture();
assert_eq!(fixture["schema"], "supercode.frontend.conformance.v2");
assert_eq!(fixture["contract"], "supercode.frontend.contract.v2");
assert_eq!(
fixture["bindings"],
serde_json::json!([
"local_rust",
"http_sse_rust",
"websocket_rust",
"acp_rust",
"http_sse_typescript",
"websocket_typescript"
])
);
assert_eq!(
fixture["generated_call_surfaces"],
serde_json::json!(["rust", "typescript"])
);
assert_eq!(fixture["session_id"], "stable-sdk-session");
assert_eq!(fixture["errors"]["competing_input"], "busy");
assert_eq!(
fixture["errors"]["unsupported_response"],
"unsupported_action"
);
assert_eq!(
fixture["event_kinds"],
serde_json::json!([
"user_message",
"turn_started",
"text_delta",
"usage",
"turn_completed",
"turn_succeeded"
])
);
assert_eq!(fixture["history"]["cursor"], 4);
assert_eq!(fixture["replay"].as_array().unwrap().len(), 2);
assert_eq!(fixture["live"].as_array().unwrap().len(), 2);
assert_eq!(fixture["request"]["kind"], "approval");
assert_eq!(
fixture["descriptor"]["extensions"]["future_client_field"]["retained"],
true
);
assert_eq!(
fixture["semantic_actions"]
.as_array()
.unwrap()
.iter()
.map(|action| action["method"].as_str().unwrap())
.collect::<Vec<_>>(),
vec![
"frontend.v2.describe",
"frontend.v2.attach",
"frontend.v2.send_input",
"frontend.v2.respond",
"frontend.v2.detach",
]
);
let contract_names = contract()["types"]["FrontendErrorName"]["values"]
.as_array()
.unwrap()
.clone();
assert_eq!(fixture["errors"]["named"], Value::Array(contract_names));
let descriptor: FrontendRuntimeDescriptor =
serde_json::from_value(fixture["descriptor"].clone()).unwrap();
assert_eq!(
descriptor.extensions["future_client_field"]["retained"],
true
);
let history: Vec<ChatMessage> =
serde_json::from_value(fixture["history"]["messages"].clone()).unwrap();
assert_eq!(history.len(), 3);
let request: FrontendRequest = serde_json::from_value(fixture["request"].clone()).unwrap();
assert_eq!(request.id, 77);
let replay: Vec<FrontendEvent> = serde_json::from_value(fixture["replay"].clone()).unwrap();
assert_eq!(replay[0].payload["request"]["payload"], request.payload);
let response: FrontendResponse =
serde_json::from_value(fixture["semantic_actions"][3]["params"]["response"].clone())
.unwrap();
assert_eq!(serde_json::to_value(response).unwrap()["request_id"], 77);
}