blob: 311795deaf055d6381c6c5042af5f56c0e13d727 [file] [log] [blame]
// 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 types of context in which should be included in a prompt.
enum IncludeContext { none, thisService, allServers }
/// A request from the server to sample an LLM via the client.
///
/// The client has full discretion over which model to select. The client should
/// also inform the user before beginning sampling, to allow them to inspect
/// the request (human in the loop) and decide whether to approve it.
extension type CreateMessageRequest.fromMap(Map<String, Object?> _value)
implements Request {
static const methodName = 'sampling/createMessage';
factory CreateMessageRequest({
required List<SamplingMessage> messages,
ModelPreferences? modelPreferences,
String? systemPrompt,
IncludeContext? includeContext,
int? temperature,
required int maxTokens,
List<String>? stopSequences,
Map<String, Object?>? metadata,
MetaWithProgressToken? meta,
}) => CreateMessageRequest.fromMap({
'messages': messages,
if (modelPreferences != null) 'modelPreferences': modelPreferences,
if (systemPrompt != null) 'systemPrompt': systemPrompt,
if (includeContext != null) 'includeContext': includeContext.name,
if (temperature != null) 'temperature': temperature,
'maxTokens': maxTokens,
if (stopSequences != null) 'stopSequences': stopSequences,
if (metadata != null) 'metadata': metadata,
if (meta != null) '_meta': meta,
});
/// The messages to send to the LLM.
List<SamplingMessage> get messages =>
(_value['messages'] as List).cast<SamplingMessage>();
/// The server's preferences for which model to select.
///
/// The client MAY ignore these preferences.
ModelPreferences? get modelPreferences =>
_value['modelPreferences'] as ModelPreferences?;
/// An optional system prompt the server wants to use for sampling.
///
/// The client MAY modify or omit this prompt.
String? get systemPrompt => _value['systemPrompt'] as String?;
/// A request to include context from one or more MCP servers (including
/// the caller), to be attached to the prompt.
///
/// The client MAY ignore this request.
IncludeContext? get includeContext {
final includeContext = _value['includeContext'] as String?;
if (includeContext == null) return null;
return IncludeContext.values.firstWhere((c) => c.name == includeContext);
}
/// The temperature to use for sampling.
double? get temperature => _value['temperature'] as double?;
/// The maximum number of tokens to sample, as requested by the server.
///
/// The client MAY choose to sample fewer tokens than requested.
int get maxTokens => _value['maxTokens'] as int;
/// Note: This has no documentation in the specification or schema.
List<String>? get stopSequences =>
(_value['stopSequences'] as List?)?.cast<String>();
/// Optional metadata to pass through to the LLM provider.
///
/// The format of this metadata is provider-specific.
Map<String, Object?>? get metadata =>
(_value['metadata'] as Map?)?.cast<String, Object?>();
}
/// The client's response to a sampling/create_message request from the
/// server.
///
/// The client should inform the user before returning the sampled message, to
/// allow them to inspect the response (human in the loop) and decide whether
/// to allow the server to see it.
extension type CreateMessageResult.fromMap(Map<String, Object?> _value)
implements Result, SamplingMessage {
factory CreateMessageResult({
required Role role,
required Content content,
required String model,
String? stopReason,
Meta? meta,
}) => CreateMessageResult.fromMap({
'role': role.name,
'content': content,
'model': model,
if (stopReason != null) 'stopReason': stopReason,
if (meta != null) '_meta': meta,
});
/// The name of the model that generated the message.
String get model => _value['model'] as String;
/// The reason why sampling stopped, if known.
///
/// Known reasons are "endTurn", "stopSequence", "maxTokens", or any other
/// reason.
String? get stopReason => _value['stopReason'] as String?;
}
/// Describes a message issued to or received from an LLM API.
extension type SamplingMessage.fromMap(Map<String, Object?> _value) {
factory SamplingMessage({required Role role, required Content content}) =>
SamplingMessage.fromMap({'role': role.name, 'content': content});
/// The role of the message.
Role get role =>
Role.values.firstWhere((role) => role.name == _value['role']);
/// The content of the message.
Content get content => _value['content'] as Content;
}
/// The server's preferences for model selection, requested of the client
/// during sampling.
///
/// Because LLMs can vary along multiple dimensions, choosing the "best" model
/// is rarely straightforward. Different models excel in different areas—some
/// are faster but less capable, others are more capable but more expensive,
/// and so on. This interface allows servers to express their priorities
/// across multiple dimensions to help clients make an appropriate selection
/// for their use case.
///
/// These preferences are always advisory. The client MAY ignore them. It is
/// also up to the client to decide how to interpret these preferences and
/// how to balance them against other considerations.
extension type ModelPreferences.fromMap(Map<String, Object?> _value) {
factory ModelPreferences({
List<ModelHint>? hints,
double? costPriority,
double? speedPriority,
double? intelligencePriority,
}) => ModelPreferences.fromMap({
if (hints != null) 'hints': hints,
if (costPriority != null) 'costPriority': costPriority,
if (speedPriority != null) 'speedPriority': speedPriority,
if (intelligencePriority != null)
'intelligencePriority': intelligencePriority,
});
/// Optional hints to use for model selection.
///
/// If multiple hints are specified, the client MUST evaluate them in order
/// (such that the first match is taken).
///
/// The client SHOULD prioritize these hints over the numeric priorities,
/// but MAY still use the priorities to select from ambiguous matches.
List<ModelHint>? get hints => (_value['hints'] as List?)?.cast<ModelHint>();
/// How much to prioritize cost when selecting a model.
///
/// A value of 0 means cost is not important, while a value of 1 means cost
/// is the most important factor.
double? get costPriority => _value['costPriority'] as double?;
/// How much to prioritize sampling speed (latency) when selecting a model.
///
/// A value of 0 means speed is not important, while a value of 1 means speed
/// is the most important factor.
double? get speedPriority => _value['speedPriority'] as double?;
/// How much to prioritize intelligence and capabilities when selecting a
/// model.
///
/// A value of 0 means intelligence is not important, while a value of 1
/// means intelligence is the most important factor.
double? get intelligencePriority => _value['intelligencePriority'] as double?;
}
/// Hints to use for model selection.
///
/// Keys not declared here are currently left unspecified by the spec and are
/// up to the client to interpret.
extension type ModelHint.fromMap(Map<String, Object?> _value) {
factory ModelHint({String? name}) =>
ModelHint.fromMap({if (name != null) 'name': name});
/// A hint for a model name.
///
/// The client SHOULD treat this as a substring of a model name; for
/// example:
/// - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`
/// - `sonnet` should match `claude-3-5-sonnet-20241022`,
/// `claude-3-sonnet-20240229`, etc.
/// - `claude` should match any Claude model
///
/// The client MAY also map the string to a different provider's model name
/// or a different model family, as long as it fills a similar niche; for
/// example:
/// - `gemini-1.5-flash` could match `claude-3-haiku-20240307`
String? get name => _value['name'] as String?;
}