1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Core\Schema\Sampling;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\ParsesNumber;
19:
20: /**
21: * The server's preferences for model selection, requested of the client during sampling.
22: *
23: * Because LLMs can vary along multiple dimensions, choosing the "best" model is
24: * rarely straightforward. Different models excel in different areas—some are
25: * faster but less capable, others are more capable but more expensive, and so
26: * on. This interface allows servers to express their priorities across multiple
27: * dimensions to help clients make an appropriate selection for their use case.
28: *
29: * These preferences are always advisory. The client MAY ignore them. It is also
30: * up to the client to decide how to interpret these preferences and how to
31: * balance them against other considerations.
32: *
33: * @implements Arrayable<array{
34: * hints?: list<template-type<ModelHint, Arrayable, 'T'>>,
35: * costPriority?: float,
36: * speedPriority?: float,
37: * intelligencePriority?: float,
38: * }>
39: *
40: * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#modelpreferences
41: */
42: final readonly class ModelPreferences implements Arrayable
43: {
44: use ParsesNumber;
45:
46: /**
47: * @var null|list<ModelHint>
48: */
49: public ?array $hints;
50:
51: /**
52: * @param null|list<ModelHint> $hints
53: */
54: public function __construct(
55: ?array $hints = null,
56: public ?float $costPriority = null,
57: public ?float $speedPriority = null,
58: public ?float $intelligencePriority = null,
59: ) {
60: if (null !== $hints) {
61: Assert::that($hints)->values()->isInstanceOf(ModelHint::class);
62: }
63:
64: Assert::that($costPriority)->nullOr()->isBetween(0.0, 1.0, message: '"modelPreferences.costPriority" must be between 0.0 and 1.0.');
65: Assert::that($speedPriority)->nullOr()->isBetween(0.0, 1.0, message: '"modelPreferences.speedPriority" must be between 0.0 and 1.0.');
66: Assert::that($intelligencePriority)->nullOr()->isBetween(0.0, 1.0, message: '"modelPreferences.intelligencePriority" must be between 0.0 and 1.0.');
67:
68: $this->hints = $hints;
69: }
70:
71: /**
72: * @param array<string, mixed> $data
73: */
74: #[\Override]
75: public static function fromArray(array $data): static
76: {
77: $hints = null;
78:
79: if (isset($data['hints'])) {
80: Assert::that($data['hints'])
81: ->isList('"modelPreferences.hints" must be a list, {type} given.')
82: ->values()
83: ->isArray('each "modelPreferences.hints" must be an object, {type} given.')
84: ->isMap('each "modelPreferences.hints" must be a string-keyed object.')
85: ;
86: $hints = array_map(ModelHint::fromArray(...), $data['hints']);
87: }
88:
89: $costPriority = $data['costPriority'] ?? null;
90:
91: if (null !== $costPriority) {
92: $costPriority = self::parseNumber($costPriority, '"modelPreferences.costPriority" must be a number or null, {type} given.');
93: }
94:
95: $speedPriority = $data['speedPriority'] ?? null;
96:
97: if (null !== $speedPriority) {
98: $speedPriority = self::parseNumber($speedPriority, '"modelPreferences.speedPriority" must be a number or null, {type} given.');
99: }
100:
101: $intelligencePriority = $data['intelligencePriority'] ?? null;
102:
103: if (null !== $intelligencePriority) {
104: $intelligencePriority = self::parseNumber($intelligencePriority, '"modelPreferences.intelligencePriority" must be a number or null, {type} given.');
105: }
106:
107: return new self($hints, $costPriority, $speedPriority, $intelligencePriority);
108: }
109:
110: #[\Override]
111: public function toArray(): array
112: {
113: $data = [];
114:
115: if (null !== $this->hints) {
116: $data['hints'] = array_map(static fn(ModelHint $hint): array => $hint->toArray(), $this->hints);
117: }
118:
119: if (null !== $this->costPriority) {
120: $data['costPriority'] = $this->costPriority;
121: }
122:
123: if (null !== $this->speedPriority) {
124: $data['speedPriority'] = $this->speedPriority;
125: }
126:
127: if (null !== $this->intelligencePriority) {
128: $data['intelligencePriority'] = $this->intelligencePriority;
129: }
130:
131: return $data;
132: }
133:
134: #[\Override]
135: public function jsonSerialize(): array|\stdClass
136: {
137: $data = [];
138:
139: if (null !== $this->hints) {
140: $data['hints'] = array_map(
141: static fn(ModelHint $hint): array|\stdClass => $hint->jsonSerialize(),
142: $this->hints,
143: );
144: }
145:
146: if (null !== $this->costPriority) {
147: $data['costPriority'] = $this->costPriority;
148: }
149:
150: if (null !== $this->speedPriority) {
151: $data['speedPriority'] = $this->speedPriority;
152: }
153:
154: if (null !== $this->intelligencePriority) {
155: $data['intelligencePriority'] = $this->intelligencePriority;
156: }
157:
158: return [] === $data ? new \stdClass() : $data;
159: }
160: }
161: