blob: 586aa534a4df3e284131a0a8534c3392344816c3 [file] [edit]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of 'api.dart';
/// The mode of an elicitation request.
enum ElicitationMode { form, url }
/// The parameters for an `elicitation/create` request.
extension type ElicitRequest._fromMap(Map<String, Object?> _value)
implements Request {
static const methodName = 'elicitation/create';
/// Creates a form-mode elicitation request.
static const form = ElicitRequest.new;
@Deprecated('Use `ElicitRequest.form` instead.')
factory ElicitRequest({
required String message,
required ObjectSchema requestedSchema,
MetaWithProgressToken? meta,
}) {
assert(
validateRequestedSchema(requestedSchema),
'Invalid requestedSchema. Must be a flat object of primitive values.',
);
return ElicitRequest._fromMap({
Keys.mode: ElicitationMode.form.name,
Keys.message: message,
Keys.requestedSchema: requestedSchema,
if (meta != null) Keys.meta: meta,
});
}
/// Creates a URL-mode elicitation request.
factory ElicitRequest.url({
required String message,
required String url,
required String elicitationId,
MetaWithProgressToken? meta,
}) {
return ElicitRequest._fromMap({
Keys.mode: ElicitationMode.url.name,
Keys.message: message,
Keys.url: url,
Keys.elicitationId: elicitationId,
if (meta != null) Keys.meta: meta,
});
}
/// The mode of this elicitation.
ElicitationMode get mode {
final mode = _value[Keys.mode] as String?;
// Default to form for backward compatibility unless specified.
if (mode == null) return ElicitationMode.form;
return ElicitationMode.values.firstWhere((value) => value.name == mode);
}
/// A message to display to the user when collecting the response.
String get message {
final message = _value[Keys.message] as String?;
if (message == null) {
throw ArgumentError('Missing required message field in $ElicitRequest');
}
return message;
}
/// A unique identifier for the elicitation.
///
/// Required for [ElicitationMode.url].
String? get elicitationId => _value[Keys.elicitationId] as String?;
/// The URL that the user should navigate to.
///
/// Required for [ElicitationMode.url].
String? get url => _value[Keys.url] as String?;
/// A JSON schema that describes the expected response.
///
/// Required for [ElicitationMode.form].
ObjectSchema? get requestedSchema =>
_value[Keys.requestedSchema] as ObjectSchema?;
/// Validates the [schema] to make sure that it conforms to the
/// limitations of the spec.
///
/// See also: [requestedSchema] for a description of the spec limitations.
static bool validateRequestedSchema(ObjectSchema schema) {
if (schema.type != JsonType.object) {
return false;
}
final properties = schema.properties;
if (properties == null) {
return true; // No properties to validate.
}
for (final propertySchema in properties.values) {
// Combinators would mean it's not a simple primitive type.
if (propertySchema.allOf != null ||
propertySchema.anyOf != null ||
propertySchema.oneOf != null ||
propertySchema.not != null) {
return false;
}
switch (propertySchema.type) {
case JsonType.string:
case JsonType.num:
case JsonType.int:
case JsonType.bool:
case JsonType
.enumeration: // ignore: deprecated_member_use_from_same_package
break;
case JsonType.object:
case JsonType.list:
case JsonType.nil:
case null:
// Disallowed, or no type specified.
return false;
}
}
return true;
}
}
/// The client's response to an `elicitation/create` request.
extension type ElicitResult.fromMap(Map<String, Object?> _value)
implements Result {
factory ElicitResult({
required ElicitationAction action,
Map<String, Object?>? content,
}) => ElicitResult.fromMap({Keys.action: action.name, Keys.content: content});
/// The action taken by the user in response to an elicitation request.
///
/// - [ElicitationAction.accept]: The user accepted the request and provided
/// the requested information.
/// - [ElicitationAction.decline]: The user explicitly declined the action.
/// - [ElicitationAction.cancel]: The user dismissed without making an
/// explicit choice.
ElicitationAction get action {
var action = _value[Keys.action] as String?;
if (action == null) {
throw ArgumentError('Missing required action field in $ElicitResult');
}
// There was a bug in the initial schema, where the `decline` action was
// named `reject` instead. Handle using that as an alias for `decline` in
// case some clients use the old name.
if (action == 'reject') action = Keys.decline;
return ElicitationAction.values.firstWhere((value) => value.name == action);
}
/// The content of the response, if the user accepted the request.
///
/// Must be `null` if the user didn't accept the request, or if it was a
/// URL-mode elicitation.
///
/// The content must conform to the [ElicitRequest]'s `requestedSchema` in
/// form mode.
Map<String, Object?>? get content =>
_value[Keys.content] as Map<String, Object?>?;
}
/// The action taken by the user in response to an elicitation request.
enum ElicitationAction {
/// The user accepted the request and provided the requested information.
accept,
/// The user explicitly declined the action.
decline,
/// The user dismissed without making an explicit choice.
cancel;
@Deprecated('Use `ElicitationAction.decline` instead.')
static const reject = decline;
}
/// A notification from the server to the client that a URL elicitation has
/// completed.
extension type ElicitationCompleteNotification.fromMap(
Map<String, Object?> _value
) implements Notification {
static const methodName = 'notifications/elicitation/complete';
factory ElicitationCompleteNotification({
required String elicitationId,
Meta? meta,
}) => ElicitationCompleteNotification.fromMap({
Keys.elicitationId: elicitationId,
if (meta != null) Keys.meta: meta,
});
/// The identifier of the completed elicitation.
String get elicitationId {
final id = _value[Keys.elicitationId] as String?;
if (id == null) {
throw ArgumentError(
'Missing elicitationId in $ElicitationCompleteNotification',
);
}
return id;
}
}