use super::*;
#[derive(Clone)]
struct VecRegistry(Vec<RemoteToolGrant>);
impl RemoteToolRegistry for VecRegistry {
fn grants(&self) -> Vec<RemoteToolGrant> {
self.0.clone()
}
}
#[test]
fn remote_llm_request_json_round_trips() {
let request = RemoteLlmRequest {
protocol_version: REMOTE_PROTOCOL_VERSION,
request_id: "request-1".to_string(),
model_intent: RemoteModelIntent::new("gpt-test"),
messages: vec![RemoteLlmMessage {
role: RemoteLlmRole::User,
content: vec![RemoteLlmContentBlock::Text {
text: "hello".to_string(),
response_meta: None,
cache_breakpoint: false,
}],
}],
attachments: vec![RemoteLlmAttachment {
id: Some("img".to_string()),
mime: "image/png".to_string(),
data_base64: Some("AQID".to_string()),
reference: None,
metadata: HashMap::new(),
}],
tools: Vec::new(),
tool_choice: RemoteLlmToolChoice::Auto,
output_spec: Some(RemoteLlmOutputSpec::JsonObject),
generation: RemoteGenerationOptions {
output_token_cap: Some(128),
..Default::default()
},
request_metadata: RemoteLlmRequestMetadata {
session_id: Some("session".to_string()),
idempotency_key: Some("idem".to_string()),
trace_id: None,
activity_cursor: None,
},
metadata: HashMap::new(),
};
request.validate().expect("valid request");
let value = serde_json::to_value(&request).expect("serialize");
let decoded: RemoteLlmRequest = serde_json::from_value(value).expect("deserialize");
assert_eq!(decoded.protocol_version, 2);
assert_eq!(decoded.request_id, request.request_id);
assert_eq!(decoded.messages, request.messages);
}
#[test]
fn remote_llm_response_json_round_trips() {
let response = RemoteLlmResponse {
protocol_version: REMOTE_PROTOCOL_VERSION,
request_id: "request-1".to_string(),
full_text: "done".to_string(),
output_parts: vec![RemoteLlmOutputPart::Text {
text: "done".to_string(),
response_meta: None,
}],
usage: RemoteUsage {
input_tokens: 1,
output_tokens: 2,
cached_input_tokens: 0,
reasoning_tokens: 0,
},
terminal_reason: RemoteLlmTerminalReason::Stop,
diagnostics: Vec::new(),
provider_metadata: RemoteProviderMetadata::default(),
};
response.validate().expect("valid response");
let value = serde_json::to_value(&response).expect("serialize");
let decoded: RemoteLlmResponse = serde_json::from_value(value).expect("deserialize");
assert_eq!(decoded.protocol_version, 2);
assert_eq!(decoded.full_text, "done");
}
#[test]
fn remote_turn_request_json_round_trips() {
let request = RemoteTurnRequest {
protocol_version: REMOTE_PROTOCOL_VERSION,
session_id: "session".to_string(),
turn_id: "turn".to_string(),
idempotency_key: Some("idem".to_string()),
input: RemoteTurnInput {
protocol_version: REMOTE_PROTOCOL_VERSION,
items: vec![
RemoteInputItem::Text {
text: "first".to_string(),
},
RemoteInputItem::ImageRef {
id: "img".to_string(),
},
],
image_blobs_base64: HashMap::from([("img".to_string(), "AQID".to_string())]),
protocol_turn_options: Some(RemoteProtocolTurnOptions {
payload: serde_json::json!({ "answer": "raw" }),
}),
trace_turn_id: Some("trace".to_string()),
prompt_layer: Some(RemotePromptLayer::new()),
},
tool_grants: vec![demo_grant("demo", "tools", "search")],
model_intent: Some(RemoteModelIntent::new("gpt-test")),
activity_cursor: Some("cursor".to_string()),
metadata: HashMap::new(),
};
request.validate().expect("valid request");
let value = serde_json::to_value(&request).expect("serialize");
let decoded: RemoteTurnRequest = serde_json::from_value(value).expect("deserialize");
assert_eq!(decoded.protocol_version, 2);
assert_eq!(decoded.session_id, "session");
assert_eq!(decoded.input.image_blobs_base64["img"], "AQID");
assert_eq!(decoded.tool_grants.len(), 1);
}
#[test]
fn remote_turn_result_json_round_trips() {
let result = RemoteTurnResult {
protocol_version: REMOTE_PROTOCOL_VERSION,
session_id: "session".to_string(),
turn_id: "turn".to_string(),
status: RemoteTurnStatus::Completed,
outcome: RemoteTurnOutcome::Finished {
finish: RemoteTurnFinish::AssistantMessage {
text: "done".to_string(),
},
},
assistant_output: RemoteAssistantOutput {
safe_text: "done".to_string(),
raw_text: "done".to_string(),
state: RemoteAssistantOutputState::Usable,
},
usage: RemoteTurnUsageSummary::default(),
execution: RemoteExecutionSummary::default(),
tool_calls: vec![RemoteToolCallSummary {
call_id: Some("call".to_string()),
tool_name: "demo".to_string(),
args: serde_json::json!({"x": 1}),
outcome: RemoteToolCallOutcome::Success(serde_json::json!({"ok": true})),
duration_ms: 5,
}],
issues: Vec::new(),
activities: vec![RemoteTurnActivity {
protocol_version: REMOTE_PROTOCOL_VERSION,
sequence: 1,
id: "event".to_string(),
correlation_id: "corr".to_string(),
event: RemoteTurnEvent::AssistantProseDelta {
text: "done".to_string(),
},
}],
metadata: HashMap::new(),
};
result.validate().expect("valid result");
let value = serde_json::to_value(&result).expect("serialize");
let decoded: RemoteTurnResult = serde_json::from_value(value).expect("deserialize");
assert_eq!(decoded.protocol_version, 2);
assert_eq!(decoded.session_id, "session");
assert_eq!(decoded.tool_calls.len(), 1);
}
#[test]
fn wrong_protocol_versions_are_rejected() {
let mut input = RemoteTurnInput::text("hello");
input.protocol_version = REMOTE_PROTOCOL_VERSION + 1;
assert!(matches!(
input.validate(),
Err(RemoteProtocolError::UnsupportedProtocolVersion { .. })
));
let mut grant = demo_grant("one", "tools", "search");
grant.protocol_version = REMOTE_PROTOCOL_VERSION + 1;
assert!(matches!(
grant.validate(),
Err(RemoteProtocolError::UnsupportedProtocolVersion { .. })
));
let request = RemoteToolCallRequest {
protocol_version: REMOTE_PROTOCOL_VERSION + 1,
tool_name: "demo".to_string(),
call_path: "tools.demo".to_string(),
args: serde_json::Value::Null,
session_id: "session".to_string(),
tool_call_id: None,
replay_key: None,
attempt_number: 1,
max_attempts: 1,
headers: HashMap::new(),
};
assert!(matches!(
request.validate(),
Err(RemoteProtocolError::UnsupportedProtocolVersion { .. })
));
let response = RemoteToolCallResponse::Success {
protocol_version: REMOTE_PROTOCOL_VERSION + 1,
value: serde_json::Value::Null,
};
assert!(matches!(
response.validate(),
Err(RemoteProtocolError::UnsupportedProtocolVersion { .. })
));
let activity = RemoteTurnActivity {
protocol_version: REMOTE_PROTOCOL_VERSION + 1,
sequence: 1,
id: "event".to_string(),
correlation_id: "corr".to_string(),
event: RemoteTurnEvent::AssistantProseDelta {
text: "hi".to_string(),
},
};
assert!(matches!(
activity.validate(),
Err(RemoteProtocolError::UnsupportedProtocolVersion { .. })
));
}
#[test]
fn nested_protocol_versions_must_match_envelope() {
let mut request = RemoteTurnRequest {
protocol_version: REMOTE_PROTOCOL_VERSION,
session_id: "session".to_string(),
turn_id: "turn".to_string(),
idempotency_key: None,
input: RemoteTurnInput::text("hello"),
tool_grants: Vec::new(),
model_intent: None,
activity_cursor: None,
metadata: HashMap::new(),
};
request.input.protocol_version = REMOTE_PROTOCOL_VERSION + 1;
assert!(matches!(
request.validate(),
Err(RemoteProtocolError::MismatchedNestedProtocolVersion { .. })
));
}
#[test]
fn top_level_protocol_schema_exports_include_versions() {
assert_schema_has_protocol_version::<RemoteLlmRequest>();
assert_schema_has_protocol_version::<RemoteLlmResponse>();
assert_schema_has_protocol_version::<RemoteTurnInput>();
assert_schema_has_protocol_version::<RemoteTurnRequest>();
assert_schema_has_protocol_version::<RemoteTurnResult>();
assert_schema_has_protocol_version::<RemoteToolGrant>();
assert_schema_has_protocol_version::<RemoteToolCallRequest>();
assert_schema_has_protocol_version::<RemoteToolCallResponse>();
assert_schema_has_protocol_version::<RemoteTurnActivity>();
}
#[test]
fn remote_tool_registry_reopen_conformance_compares_call_paths() {
let before = VecRegistry(vec![demo_grant("one", "tools", "search")]);
let reopened = VecRegistry(vec![demo_grant("one", "tools", "search")]);
assert_remote_tool_registry_reopenable(&before, &reopened).expect("same registry");
let changed = VecRegistry(vec![demo_grant("one", "tools", "read")]);
assert!(matches!(
assert_remote_tool_registry_reopenable(&before, &changed),
Err(RemoteProtocolError::RemoteToolRegistryReopenMismatch { .. })
));
}
fn demo_grant(name: &str, module: &str, operation: &str) -> RemoteToolGrant {
RemoteToolGrant {
protocol_version: REMOTE_PROTOCOL_VERSION,
id: None,
name: name.to_string(),
description: "demo".to_string(),
input_schema: default_input_schema(),
output_schema: serde_json::Value::Null,
input_schema_projections: Vec::new(),
output_schema_projections: Vec::new(),
output_contract: RemoteToolOutputContract::Static,
examples: Vec::new(),
availability: None,
activation: None,
argument_projection: None,
scheduling: None,
retry_policy: None,
agent_surface: Some(RemoteToolAgentSurface::new([module], operation)),
}
}
fn assert_schema_has_protocol_version<T: JsonSchema>() {
let schema = schemars::schema_for!(T);
let schema_json = serde_json::to_value(&schema).expect("schema json");
let schema_text = schema_json.to_string();
assert!(
schema_text.contains("protocol_version"),
"schema did not include protocol_version: {schema_text}"
);
}