blob: a3f5ee587f19a9ac9dbf5867cde48f3f7f39c9ac [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::*;
use serde_json::Value;
type Result<T> = std::result::Result<T, &'static str>;
fn shape(v: &Value, keys: &[&str]) -> Result<()> {
let r = v.as_object().ok_or("Invalid interaction object")?;
if r.len() != keys.len() || keys.iter().any(|key| !r.contains_key(*key)) {
return Err("Invalid interaction shape");
}
Ok(())
}
fn field<T: serde::de::DeserializeOwned>(v: &Value, key: &str) -> Result<T> {
serde_json::from_value(v[key].clone()).map_err(|_| "Invalid interaction field")
}
fn request(v: &Value) -> Result<InteractionRequest> {
let value = match v["kind"].as_str() {
Some("service_approval") => {
shape(
v,
&[
"kind",
"toolUseId",
"toolName",
"service",
"method",
"binding",
"through",
],
)?;
InteractionRequest::ServiceApproval {
tool_use_id: field(v, "toolUseId")?,
tool_name: field(v, "toolName")?,
service: field(v, "service")?,
method: field(v, "method")?,
binding: field(v, "binding")?,
through: field(v, "through")?,
}
}
Some("permissions") => {
shape(v, &["kind", "toolUseId", "baseRevision", "request"])?;
InteractionRequest::Permissions {
tool_use_id: field(v, "toolUseId")?,
base_revision: field(v, "baseRevision")?,
request: field(v, "request")?,
}
}
Some("question") => {
shape(v, &["kind", "toolUseId", "questions"])?;
InteractionRequest::Question {
tool_use_id: field(v, "toolUseId")?,
questions: field(v, "questions")?,
}
}
Some("client_capability") => {
if v.get("baseRevision").is_some() {
shape(v, &["kind", "toolUseId", "target", "baseRevision"])?;
} else {
shape(v, &["kind", "toolUseId", "target"])?;
}
InteractionRequest::ClientCapability {
tool_use_id: field(v, "toolUseId")?,
target: field(v, "target")?,
base_revision: field(v, "baseRevision")?,
}
}
Some("form") => {
shape(v, &["kind", "toolUseId", "message", "requester", "fields"])?;
let input = crate::capability::form_decode::decode_input(&serde_json::json!({
"message": v["message"], "requester": v["requester"], "fields": v["fields"]
}))?;
InteractionRequest::Form {
tool_use_id: field(v, "toolUseId")?,
message: input.message,
requester: input.requester,
fields: input.fields,
}
}
_ => return Err("Unsupported interaction kind"),
};
value.validate()?;
Ok(value)
}
fn result(v: &Value, outcome: bool) -> Result<FormResult> {
let accept = v["action"] == "accept";
let keys: &[&str] = match (outcome, accept) {
(true, true) => &["kind", "action", "values", "committedAt"],
(true, false) => &["kind", "action", "committedAt"],
(false, true) => &["kind", "action", "values"],
(false, false) => &["kind", "action"],
};
shape(v, keys)?;
let mut r = v.clone();
let r = r.as_object_mut().ok_or("Invalid form object")?;
r.remove("kind");
r.remove("committedAt");
crate::capability::form_result_decode::decode_form_result(&Value::Object(r.clone()))
}
fn answer(v: &Value) -> Result<InteractionAnswer> {
let value = match v["kind"].as_str() {
Some("service_approval") => {
shape(v, &["kind", "decision"])?;
InteractionAnswer::ServiceApproval {
decision: field(v, "decision")?,
}
}
Some("permissions") => {
shape(v, &["kind", "decision"])?;
InteractionAnswer::Permissions {
decision: field(v, "decision")?,
}
}
Some("question") => {
shape(v, &["kind", "answers"])?;
InteractionAnswer::Question {
answers: field(v, "answers")?,
}
}
Some("client_capability") => {
shape(v, &["kind", "decision"])?;
InteractionAnswer::ClientCapability {
decision: field(v, "decision")?,
}
}
Some("form") => InteractionAnswer::Form {
result: result(v, false)?,
},
_ => return Err("Unsupported answer kind"),
};
value.validate()?;
Ok(value)
}
fn outcome(v: &Value) -> Result<InteractionOutcome> {
let committed_at = field(v, "committedAt")?;
let value = match v["kind"].as_str() {
Some("service_approval_decision") => {
shape(v, &["kind", "decision", "committedAt"])?;
InteractionOutcome::ServiceApprovalDecision {
decision: field(v, "decision")?,
committed_at,
}
}
Some("permissions_decision") => {
shape(v, &["kind", "decision", "committedAt"])?;
InteractionOutcome::PermissionsDecision {
decision: field(v, "decision")?,
committed_at,
}
}
Some("question_answer") => {
shape(v, &["kind", "answers", "committedAt"])?;
InteractionOutcome::QuestionAnswer {
answers: field(v, "answers")?,
committed_at,
}
}
Some("client_capability_decision") => {
shape(v, &["kind", "decision", "committedAt"])?;
InteractionOutcome::ClientCapabilityDecision {
decision: field(v, "decision")?,
committed_at,
}
}
Some("closure") => {
shape(v, &["kind", "reason", "committedAt"])?;
InteractionOutcome::Closure {
reason: field(v, "reason")?,
committed_at,
}
}
Some("form_answer") => InteractionOutcome::FormAnswer {
result: result(v, true)?,
committed_at,
},
_ => return Err("Unsupported outcome kind"),
};
value.validate()?;
Ok(value)
}
macro_rules! decode {
($ty:ty, $function:ident) => {
impl<'de> Deserialize<'de> for $ty {
fn deserialize<D: serde::Deserializer<'de>>(
d: D,
) -> std::result::Result<Self, D::Error> {
$function(&Value::deserialize(d)?).map_err(serde::de::Error::custom)
}
}
};
}
decode!(InteractionRequest, request);
decode!(InteractionAnswer, answer);
decode!(InteractionOutcome, outcome);