// What the client does when the server asks it something: a completion the
// model writes (sampling), a question put to the person in front of it
// (elicitation), or the roots it is allowed to read.
///|
/// A client's answers to the requests a server may make of it. Any of the three
/// may be left out, and a server that asks for one the client does not do is
/// told so with the error JSON-RPC prescribes.
///
/// `apply_defaults` is SEP-1034: an accepted elicitation leaves out a field the
/// person did not touch, and the default from the requested schema is filled in
/// before the answer goes back, so the server sees a complete form.
pub(all) struct Handlers {
sampling : (async (@types.CreateMessageRequestParams) -> @types.CreateMessageResult)?
elicitation : (async (@types.ElicitRequestParams) -> @types.ElicitResult)?
roots : (async () -> @types.ListRootsResult)?
apply_defaults : Bool
}
///|
/// The answers a client gives. Everything left out is a request the client
/// declines to serve.
pub fn Handlers::new(
sampling? : async (@types.CreateMessageRequestParams) -> @types.CreateMessageResult,
elicitation? : async (@types.ElicitRequestParams) -> @types.ElicitResult,
roots? : async () -> @types.ListRootsResult,
apply_defaults~ : Bool = true,
) -> Handlers {
{ sampling, elicitation, roots, apply_defaults, }
}
///|
/// A client that answers nothing.
pub fn Handlers::none() -> Handlers {
{ sampling: None, elicitation: None, roots: None, apply_defaults: true, }
}
///|
/// What to send back for a message the server sent: a reply when it was a
/// request, nothing when it was a notification.
pub async fn Handlers::answer(self : Handlers, message : Json) -> Json? {
guard @server.request_id_of(message) is Some(id) else { return None }
let operation = @server.method_of(message).unwrap_or("")
let params = @server.params_of(message)
match operation {
"sampling/createMessage" =>
match self.sampling {
Some(run) => {
let request : @types.CreateMessageRequestParams? = try
@json.from_json(Json(params))
catch {
_ => None
} noraise {
request => Some(request)
}
guard request is Some(request) else {
return refusal(
id, @types.invalid_params, "sampling/createMessage: bad params",
)
}
let result = run(request) catch {
e => return refusal(id, @types.internal_error, "\{e}")
}
Some({ "jsonrpc": "2.0", "id": id, "result": result.to_json() })
}
None =>
refusal(id, @types.method_not_found, "This client does not sample")
}
"elicitation/create" | "elicitation/createForm" =>
match self.elicitation {
Some(run) => {
let request : @types.ElicitRequestParams? = try
@json.from_json(Json(params))
catch {
_ => None
} noraise {
request => Some(request)
}
guard request is Some(request) else {
return refusal(
id, @types.invalid_params, "elicitation/create: bad params",
)
}
let result = run(request) catch {
e => return refusal(id, @types.internal_error, "\{e}")
}
let result = if self.apply_defaults && result.action == "accept" {
(
{
action: result.action,
content: Some(
fill_defaults(
request.requested_schema,
result.content.unwrap_or(Map([])),
),
),
meta: result.meta,
} : @types.ElicitResult)
} else {
result
}
Some({ "jsonrpc": "2.0", "id": id, "result": result.to_json() })
}
None =>
refusal(id, @types.method_not_found, "This client does not elicit")
}
"roots/list" =>
match self.roots {
Some(run) => {
let result = run() catch {
e => return refusal(id, @types.internal_error, "\{e}")
}
Some({ "jsonrpc": "2.0", "id": id, "result": result.to_json() })
}
None => refusal(id, @types.method_not_found, "This client has no roots")
}
_ => refusal(id, @types.method_not_found, "Method not found: \{operation}")
}
}
///|
/// An error reply to a request this client will not serve.
fn refusal(id : Json, code : Int32, message : String) -> Json? {
Some({
"jsonrpc": "2.0",
"id": id,
"error": {
"code": Json::number(code.to_double()),
"message": Json::string(message),
},
})
}
///|
/// `content` with the default of every property of `schema` it leaves out, one
/// level deep: a form's fields are properties of the requested schema, and a
/// nested object carries its own form. A property without a default stays out —
/// the person left it empty, which the server may treat as an error.
pub fn fill_defaults(
schema : Map[String, Json]?,
content : Map[String, Json],
) -> Map[String, Json] {
guard schema is Some(schema) &&
schema.get("properties") is Some(Object(properties)) else {
return content
}
let out = content.copy()
for property in properties {
let (name, definition) = property
guard !out.contains(name) && definition is Object(definition) else {
continue
}
match definition.get("default") {
Some(value) => out[name] = value
None => ()
}
}
out
}