///|
using @jsonrpc {type RequestId, jsonrpc_decode_int64}
///|
/// Stable wire identifier for a URL elicitation.
pub type ElicitationId = String
///|
/// Request scope for `elicitation/create`. The two variants are flattened
/// into `requestId` or `sessionId` on the wire.
pub(all) enum ElicitationScope {
Request(RequestId)
Session(session_id~ : SessionId, tool_call_id~ : ProtocolNullable[ToolCallId])
} derive(Eq, Debug)
///|
/// Form-mode elicitation parameters.
pub(all) struct ElicitationFormParams {
scope : ElicitationScope
message : String
requested_schema : Json
meta : ProtocolNullable[Json]
} derive(Eq, Debug)
///|
/// URL-mode elicitation parameters.
pub(all) struct ElicitationUrlParams {
scope : ElicitationScope
elicitation_id : ElicitationId
message : String
url : String
meta : ProtocolNullable[Json]
} derive(Eq, Debug)
///|
/// The form/URL union for `elicitation/create`.
pub(all) enum ElicitationCreateParams {
Form(ElicitationFormParams)
Url(ElicitationUrlParams)
} derive(Eq, Debug)
///|
/// The action returned by the client for `elicitation/create`.
pub(all) enum ElicitationAction {
Accept
Decline
Cancel
} derive(Eq, Debug)
///|
/// Result of `elicitation/create`.
///
/// `content` carries the accept-action form values. Only the accept outcome
/// may carry content; decline and cancel are payload-free per the fixed v1
/// schema (`CreateElicitationResponse`/`ElicitationAcceptAction`).
pub(all) struct ElicitationCreateResult {
action : ElicitationAction
content : ProtocolNullable[Json]
meta : ProtocolNullable[Json]
} derive(Eq, Debug)
///|
/// Parameters for the `elicitation/complete` notification.
pub(all) struct ElicitationCompleteParams {
elicitation_id : ElicitationId
meta : ProtocolNullable[Json]
} derive(Eq, Debug)
///|
fn elicitation_require_non_empty(
value : String,
path~ : String,
) -> String raise ProtocolDecodeError {
if value == "" {
raise InvalidField(path~, reason="string must not be empty")
}
value
}
///|
fn elicitation_decode_non_empty_string(
value : Json,
path~ : String,
) -> String raise ProtocolDecodeError {
elicitation_require_non_empty(protocol_decode_string(value, path~), path~)
}
///|
fn elicitation_decode_request_id(
value : Json,
path~ : String,
) -> RequestId raise ProtocolDecodeError {
match value {
String(value) => String(value)
Number(value, repr~) =>
Number(
jsonrpc_decode_int64(value, repr, path~) catch {
_ =>
raise InvalidField(path~, reason="expected a signed 64-bit integer")
},
)
Null => Null
_ =>
raise InvalidField(
path~,
reason="requestId must be a string, integer, or null",
)
}
}
///|
fn elicitation_encode_request_id(value : RequestId) -> Json {
match value {
String(value) => Json::string(value)
Number(value) => Json::number(value.to_double(), repr=value.to_string())
Null => Json::null()
}
}
///|
fn elicitation_decode_non_negative_integer(
value : Json,
path~ : String,
) -> UInt64 raise ProtocolDecodeError {
match value {
Number(number, repr~) =>
match protocol_parse_uint64_number(number, repr) {
Ok(value) => value
Err(NonFinite) =>
raise InvalidField(path~, reason="expected a finite integer")
Err(Fractional) =>
raise InvalidField(path~, reason="expected a non-negative integer")
Err(Malformed | OutOfRange | PrecisionUnavailable) =>
raise InvalidField(path~, reason="expected a uint64 integer")
}
_ => raise ExpectedNumber(path~)
}
}
///|
fn elicitation_decode_signed_integer(
value : Json,
path~ : String,
) -> Int64 raise ProtocolDecodeError {
match value {
Number(number, repr~) =>
match protocol_parse_int64_number(number, repr) {
Ok(value) => value
Err(NonFinite) =>
raise InvalidField(path~, reason="expected a finite integer")
Err(Fractional) =>
raise InvalidField(path~, reason="expected an integer")
Err(Malformed | OutOfRange | PrecisionUnavailable) =>
raise InvalidField(path~, reason="expected an int64 integer")
}
_ => raise ExpectedNumber(path~)
}
}
///|
fn elicitation_validate_optional_string(
fields : Map[String, Json],
key : String,
path~ : String,
) -> Unit raise ProtocolDecodeError {
match protocol_field(fields, key) {
Omitted | Null => ()
Value(value) => ignore(protocol_decode_string(value, path~))
}
}
///|
fn elicitation_validate_optional_boolean(
fields : Map[String, Json],
key : String,
path~ : String,
) -> Unit raise ProtocolDecodeError {
match protocol_field(fields, key) {
Omitted | Null => ()
Value(value) => ignore(protocol_decode_boolean(value, path~))
}
}
///|
fn elicitation_validate_optional_number(
fields : Map[String, Json],
key : String,
path~ : String,
) -> Unit raise ProtocolDecodeError {
match protocol_field(fields, key) {
Omitted | Null => ()
Value(value) => ignore(protocol_decode_number(value, path~))
}
}
///|
fn elicitation_validate_optional_non_negative_integer(
fields : Map[String, Json],
key : String,
path~ : String,
) -> Unit raise ProtocolDecodeError {
match protocol_field(fields, key) {
Omitted | Null => ()
Value(value) =>
ignore(elicitation_decode_non_negative_integer(value, path~))
}
}
///|
fn elicitation_validate_optional_integer(
fields : Map[String, Json],
key : String,
path~ : String,
) -> Unit raise ProtocolDecodeError {
match protocol_field(fields, key) {
Omitted | Null => ()
Value(value) => ignore(elicitation_decode_signed_integer(value, path~))
}
}
///|
fn elicitation_validate_string_array(
value : Json,
path~ : String,
) -> Unit raise ProtocolDecodeError {
let values = protocol_require_array(value, path~)
for index, value in values {
ignore(
protocol_decode_string(value, path=path + "[" + index.to_string() + "]"),
)
}
}
///|
fn elicitation_validate_enum_options(
value : Json,
path~ : String,
) -> Unit raise ProtocolDecodeError {
let values = protocol_require_array(value, path~)
for index, value in values {
let option_path = path + "[" + index.to_string() + "]"
let fields = protocol_require_object(value, path=option_path)
protocol_reject_unknown(
fields,
["const", "title", "description", "_meta"],
prefix=option_path,
)
ignore(protocol_required(fields, "const", path=option_path + ".const"))
elicitation_validate_optional_string(
fields,
"title",
path=option_path + ".title",
)
elicitation_validate_optional_string(
fields,
"description",
path=option_path + ".description",
)
ignore(protocol_meta(fields, path=option_path + "._meta"))
}
}
///|
fn elicitation_validate_array_items(
value : Json,
path~ : String,
) -> Unit raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
protocol_reject_unknown(
fields,
["type", "anyOf", "enum", "_meta"],
prefix=path,
)
match protocol_field(fields, "type") {
Omitted => ()
Null => raise InvalidField(path=path + ".type", reason="type is required")
Value(value) =>
match protocol_decode_string(value, path=path + ".type") {
"string" => ()
other => raise InvalidDiscriminator(path=path + ".type", value=other)
}
}
match protocol_field(fields, "anyOf") {
Omitted | Null => ()
Value(value) =>
elicitation_validate_enum_options(value, path=path + ".anyOf")
}
match protocol_field(fields, "enum") {
Omitted | Null => ()
Value(value) =>
elicitation_validate_string_array(value, path=path + ".enum")
}
ignore(protocol_meta(fields, path=path + "._meta"))
}
///|
fn elicitation_validate_property_schema(
value : Json,
path~ : String,
) -> Unit raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
let type_value = protocol_required_string(fields, "type", path=path + ".type")
match type_value {
"string" => {
protocol_reject_unknown(
fields,
[
"type", "title", "description", "default", "minLength", "maxLength", "pattern",
"format", "enum", "oneOf", "_meta",
],
prefix=path,
)
elicitation_validate_optional_string(
fields,
"title",
path=path + ".title",
)
elicitation_validate_optional_string(
fields,
"description",
path=path + ".description",
)
elicitation_validate_optional_string(
fields,
"default",
path=path + ".default",
)
elicitation_validate_optional_non_negative_integer(
fields,
"minLength",
path=path + ".minLength",
)
elicitation_validate_optional_non_negative_integer(
fields,
"maxLength",
path=path + ".maxLength",
)
elicitation_validate_optional_string(
fields,
"pattern",
path=path + ".pattern",
)
match protocol_field(fields, "format") {
Omitted | Null => ()
Value(value) => {
let format = protocol_decode_string(value, path=path + ".format")
match format {
"email" | "uri" | "date" | "date-time" => ()
other =>
raise InvalidDiscriminator(path=path + ".format", value=other)
}
}
}
match protocol_field(fields, "enum") {
Omitted | Null => ()
Value(value) =>
elicitation_validate_string_array(value, path=path + ".enum")
}
match protocol_field(fields, "oneOf") {
Omitted | Null => ()
Value(value) =>
elicitation_validate_enum_options(value, path=path + ".oneOf")
}
}
"number" | "integer" => {
protocol_reject_unknown(
fields,
[
"type", "title", "description", "default", "minimum", "maximum", "_meta",
],
prefix=path,
)
elicitation_validate_optional_string(
fields,
"title",
path=path + ".title",
)
elicitation_validate_optional_string(
fields,
"description",
path=path + ".description",
)
if type_value == "number" {
elicitation_validate_optional_number(
fields,
"default",
path=path + ".default",
)
elicitation_validate_optional_number(
fields,
"minimum",
path=path + ".minimum",
)
elicitation_validate_optional_number(
fields,
"maximum",
path=path + ".maximum",
)
} else {
elicitation_validate_optional_integer(
fields,
"default",
path=path + ".default",
)
elicitation_validate_optional_integer(
fields,
"minimum",
path=path + ".minimum",
)
elicitation_validate_optional_integer(
fields,
"maximum",
path=path + ".maximum",
)
}
}
"boolean" => {
protocol_reject_unknown(
fields,
["type", "title", "description", "default", "_meta"],
prefix=path,
)
elicitation_validate_optional_string(
fields,
"title",
path=path + ".title",
)
elicitation_validate_optional_string(
fields,
"description",
path=path + ".description",
)
elicitation_validate_optional_boolean(
fields,
"default",
path=path + ".default",
)
}
"array" => {
protocol_reject_unknown(
fields,
[
"type", "title", "description", "default", "minItems", "maxItems", "items",
"_meta",
],
prefix=path,
)
elicitation_validate_optional_string(
fields,
"title",
path=path + ".title",
)
elicitation_validate_optional_string(
fields,
"description",
path=path + ".description",
)
match protocol_field(fields, "default") {
Omitted | Null => ()
Value(value) =>
ignore(protocol_require_array(value, path=path + ".default"))
}
match protocol_field(fields, "minItems") {
Omitted | Null => ()
Value(value) =>
ignore(
elicitation_decode_non_negative_integer(
value,
path=path + ".minItems",
),
)
}
match protocol_field(fields, "maxItems") {
Omitted | Null => ()
Value(value) =>
ignore(
elicitation_decode_non_negative_integer(
value,
path=path + ".maxItems",
),
)
}
let items = protocol_required(fields, "items", path=path + ".items")
elicitation_validate_array_items(items, path=path + ".items")
}
other => raise InvalidDiscriminator(path=path + ".type", value=other)
}
ignore(protocol_meta(fields, path=path + "._meta"))
}
///|
fn elicitation_validate_requested_schema(
value : Json,
path~ : String,
) -> Json raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
protocol_reject_unknown(
fields,
["type", "properties", "required"],
prefix=path,
)
let schema_type = protocol_required_string(
fields,
"type",
path=path + ".type",
)
if schema_type != "object" {
raise InvalidDiscriminator(path=path + ".type", value=schema_type)
}
let properties = protocol_require_object(
protocol_required(fields, "properties", path=path + ".properties"),
path=path + ".properties",
)
for name, property in properties {
elicitation_validate_property_schema(
property,
path=path + ".properties." + name,
)
}
match protocol_field(fields, "required") {
Omitted | Null => ()
Value(value) =>
elicitation_validate_string_array(value, path=path + ".required")
}
value
}
///|
fn elicitation_decode_scope(
fields : Map[String, Json],
path~ : String,
) -> ElicitationScope raise ProtocolDecodeError {
let has_request = fields.contains("requestId")
let has_session = fields.contains("sessionId")
if has_request && has_session {
raise InvalidField(
path~,
reason="requestId and sessionId are mutually exclusive",
)
}
if has_request {
if fields.contains("toolCallId") {
raise InvalidField(
path=path + ".toolCallId",
reason="toolCallId requires sessionId",
)
}
Request(
elicitation_decode_request_id(
protocol_required(fields, "requestId", path=path + ".requestId"),
path=path + ".requestId",
),
)
} else if has_session {
let session_id = elicitation_decode_non_empty_string(
protocol_required(fields, "sessionId", path=path + ".sessionId"),
path=path + ".sessionId",
)
let tool_call_id : ProtocolNullable[ToolCallId] = match
protocol_field(fields, "toolCallId") {
Omitted => Omitted
Null => Null
Value(value) =>
Value(
elicitation_decode_non_empty_string(value, path=path + ".toolCallId"),
)
}
Session(session_id~, tool_call_id~)
} else {
raise MissingField(path=path + ".requestId|sessionId")
}
}
///|
fn elicitation_put_scope(
fields : Map[String, Json],
scope : ElicitationScope,
) -> Unit raise ProtocolDecodeError {
match scope {
Request(request_id) =>
fields["requestId"] = elicitation_encode_request_id(request_id)
Session(session_id~, tool_call_id~) => {
fields["sessionId"] = Json::string(
elicitation_require_non_empty(session_id, path="sessionId"),
)
match tool_call_id {
Omitted => ()
Null => fields["toolCallId"] = Json::null()
Value(value) =>
fields["toolCallId"] = Json::string(
elicitation_require_non_empty(value, path="toolCallId"),
)
}
}
}
}
///|
fn elicitation_decode_mode(
value : Json,
path~ : String,
) -> String raise ProtocolDecodeError {
match protocol_decode_string(value, path~) {
"form" => "form"
"url" => "url"
other => raise InvalidDiscriminator(path~, value=other)
}
}
///|
fn elicitation_encode_action(value : ElicitationAction) -> Json {
Json::string(
match value {
Accept => "accept"
Decline => "decline"
Cancel => "cancel"
},
)
}
///|
fn elicitation_decode_action(
value : Json,
path~ : String,
) -> ElicitationAction raise ProtocolDecodeError {
match protocol_decode_string(value, path~) {
"accept" => Accept
"decline" => Decline
"cancel" => Cancel
other => raise InvalidDiscriminator(path~, value=other)
}
}
///|
/// Validate one elicitation content value. The fixed v1 schema allows
/// strings, integers, numbers, booleans, and arrays of strings; nulls and
/// nested objects are rejected instead of silently dropped.
fn elicitation_validate_content_value(
value : Json,
path~ : String,
) -> Unit raise ProtocolDecodeError {
match value {
String(_) => ()
True | False => ()
Array(values) =>
for index, item in values {
match item {
String(_) => ()
_ =>
raise InvalidField(
path=path + "[" + index.to_string() + "]",
reason="content array values must be strings",
)
}
}
Null | Object(_) =>
raise InvalidField(
path~,
reason="content values must be primitives or string arrays",
)
_ => ignore(protocol_decode_number(value, path~))
}
}
///|
/// Validate the accept-action content object keyed by form field names and
/// return it unchanged.
fn elicitation_validate_content(
value : Json,
path~ : String,
) -> Json raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
for name, item in fields {
elicitation_validate_content_value(item, path=path + "." + name)
}
value
}
///|
/// Decode the form/URL union for `elicitation/create`.
pub fn elicitation_create_params_from_json(
value : Json,
path? : String = "elicitation/create",
) -> ElicitationCreateParams raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
protocol_reject_unknown(
fields,
[
"mode", "message", "requestedSchema", "elicitationId", "url", "requestId",
"sessionId", "toolCallId", "_meta",
],
prefix=path,
)
let mode = elicitation_decode_mode(
protocol_required(fields, "mode", path=path + ".mode"),
path=path + ".mode",
)
let message = protocol_required_string(
fields,
"message",
path=path + ".message",
)
let scope = elicitation_decode_scope(fields, path~)
let meta = protocol_meta(fields, path=path + "._meta")
match mode {
"form" => {
if fields.contains("elicitationId") || fields.contains("url") {
raise InvalidField(path~, reason="form mode cannot contain URL fields")
}
let requested_schema = elicitation_validate_requested_schema(
protocol_required(
fields,
"requestedSchema",
path=path + ".requestedSchema",
),
path=path + ".requestedSchema",
)
Form({ scope, message, requested_schema, meta })
}
"url" => {
if fields.contains("requestedSchema") {
raise InvalidField(
path~,
reason="url mode cannot contain requestedSchema",
)
}
let elicitation_id = elicitation_decode_non_empty_string(
protocol_required(fields, "elicitationId", path=path + ".elicitationId"),
path=path + ".elicitationId",
)
let url = elicitation_decode_non_empty_string(
protocol_required(fields, "url", path=path + ".url"),
path=path + ".url",
)
Url({ scope, elicitation_id, message, url, meta })
}
_ => abort("unreachable")
}
}
///|
/// Encode the form/URL union for `elicitation/create`.
pub fn elicitation_create_params_to_json(
value : ElicitationCreateParams,
) -> Json raise ProtocolDecodeError {
let fields : Map[String, Json] = Map([])
match value {
Form(value) => {
fields["mode"] = Json::string("form")
fields["message"] = Json::string(value.message)
elicitation_put_scope(fields, value.scope)
fields["requestedSchema"] = elicitation_validate_requested_schema(
value.requested_schema,
path="requestedSchema",
)
protocol_put_meta(fields, value.meta)
}
Url(value) => {
fields["mode"] = Json::string("url")
fields["message"] = Json::string(value.message)
fields["elicitationId"] = Json::string(
elicitation_require_non_empty(
value.elicitation_id,
path="elicitationId",
),
)
fields["url"] = Json::string(
elicitation_require_non_empty(value.url, path="url"),
)
elicitation_put_scope(fields, value.scope)
protocol_put_meta(fields, value.meta)
}
}
Json::object(fields)
}
///|
/// Decode the result of `elicitation/create`. Only the accept outcome may
/// carry `content`; a decline or cancel carrying content is an explicit
/// invalid-params failure, never a silently ignored payload.
pub fn elicitation_create_result_from_json(
value : Json,
path? : String = "elicitation/create",
) -> ElicitationCreateResult raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
protocol_reject_unknown(fields, ["action", "content", "_meta"], prefix=path)
let action = elicitation_decode_action(
protocol_required(fields, "action", path=path + ".action"),
path=path + ".action",
)
let content : ProtocolNullable[Json] = match
protocol_field(fields, "content") {
Omitted => Omitted
Null =>
match action {
Accept => Null
Decline | Cancel =>
raise InvalidField(
path=path + ".content",
reason="decline and cancel must not carry content",
)
}
Value(content) =>
match action {
Accept =>
Value(elicitation_validate_content(content, path=path + ".content"))
Decline | Cancel =>
raise InvalidField(
path=path + ".content",
reason="decline and cancel must not carry content",
)
}
}
let meta = protocol_meta(fields, path=path + "._meta")
{ action, content, meta }
}
///|
/// Encode the result of `elicitation/create`. Encoding a decline or cancel
/// result whose `content` is set fails fast instead of dropping the payload.
pub fn elicitation_create_result_to_json(
value : ElicitationCreateResult,
) -> Json raise ProtocolDecodeError {
let fields : Map[String, Json] = Map([])
fields["action"] = elicitation_encode_action(value.action)
match value.action {
Accept =>
match value.content {
Omitted => ()
Null => fields["content"] = Json::null()
Value(content) =>
fields["content"] = elicitation_validate_content(
content,
path="content",
)
}
Decline | Cancel =>
match value.content {
Omitted => ()
Null | Value(_) =>
raise InvalidField(
path="content",
reason="decline and cancel must not carry content",
)
}
}
protocol_put_meta(fields, value.meta)
Json::object(fields)
}
///|
/// Decode the `elicitation/complete` notification parameters.
pub fn elicitation_complete_params_from_json(
value : Json,
path? : String = "elicitation/complete",
) -> ElicitationCompleteParams raise ProtocolDecodeError {
let fields = protocol_require_object(value, path~)
protocol_reject_unknown(fields, ["elicitationId", "_meta"], prefix=path)
let elicitation_id = elicitation_decode_non_empty_string(
protocol_required(fields, "elicitationId", path=path + ".elicitationId"),
path=path + ".elicitationId",
)
let meta = protocol_meta(fields, path=path + "._meta")
{ elicitation_id, meta }
}
///|
/// Encode the `elicitation/complete` notification parameters.
pub fn elicitation_complete_params_to_json(
value : ElicitationCompleteParams,
) -> Json raise ProtocolDecodeError {
let fields : Map[String, Json] = Map([])
fields["elicitationId"] = Json::string(
elicitation_require_non_empty(value.elicitation_id, path="elicitationId"),
)
protocol_put_meta(fields, value.meta)
Json::object(fields)
}