| 1: | <?php |
| 2: | |
| 3: | declare(strict_types=1); |
| 4: | |
| 5: | |
| 6: | |
| 7: | |
| 8: | |
| 9: | |
| 10: | |
| 11: | |
| 12: | |
| 13: | |
| 14: | namespace Nexus\Mcp\Core\Schema\RequestParams; |
| 15: | |
| 16: | use Nexus\Assert\Assert; |
| 17: | use Nexus\Mcp\Core\Schema\Enum\IncludeContext; |
| 18: | use Nexus\Mcp\Core\Schema\ParsesNumber; |
| 19: | use Nexus\Mcp\Core\Schema\RequestMetaObject; |
| 20: | use Nexus\Mcp\Core\Schema\RequestParams; |
| 21: | use Nexus\Mcp\Core\Schema\Sampling\ModelPreferences; |
| 22: | use Nexus\Mcp\Core\Schema\Sampling\SamplingMessage; |
| 23: | use Nexus\Mcp\Core\Schema\Sampling\ToolChoice; |
| 24: | use Nexus\Mcp\Core\Schema\Task\TaskMetadata; |
| 25: | use Nexus\Mcp\Core\Schema\Tool\Tool; |
| 26: | use Nexus\Mcp\Core\Validation\EnumValueValidator; |
| 27: | |
| 28: | |
| 29: | |
| 30: | |
| 31: | |
| 32: | |
| 33: | final readonly class CreateMessageRequestParams extends RequestParams |
| 34: | { |
| 35: | use ParsesNumber; |
| 36: | |
| 37: | |
| 38: | |
| 39: | |
| 40: | public array $messages; |
| 41: | |
| 42: | |
| 43: | |
| 44: | |
| 45: | public ?string $systemPrompt; |
| 46: | |
| 47: | |
| 48: | |
| 49: | |
| 50: | public ?array $stopSequences; |
| 51: | |
| 52: | |
| 53: | |
| 54: | |
| 55: | public ?array $metadata; |
| 56: | |
| 57: | |
| 58: | |
| 59: | |
| 60: | public ?array $tools; |
| 61: | |
| 62: | |
| 63: | |
| 64: | |
| 65: | |
| 66: | |
| 67: | |
| 68: | public function __construct( |
| 69: | public int $maxTokens, |
| 70: | array $messages, |
| 71: | public ?IncludeContext $includeContext = null, |
| 72: | public ModelPreferences $modelPreferences = new ModelPreferences(), |
| 73: | ?array $stopSequences = null, |
| 74: | ?string $systemPrompt = null, |
| 75: | public ?TaskMetadata $task = null, |
| 76: | public ?float $temperature = null, |
| 77: | public ToolChoice $toolChoice = new ToolChoice(), |
| 78: | ?array $tools = null, |
| 79: | ?array $metadata = null, |
| 80: | RequestMetaObject $meta = new RequestMetaObject(), |
| 81: | ) { |
| 82: | Assert::that($messages)->values()->isInstanceOf(SamplingMessage::class); |
| 83: | Assert::that($systemPrompt)->nullOr()->isNonEmptyString('"params.systemPrompt" must be a non-empty string or null.'); |
| 84: | |
| 85: | if (null !== $stopSequences) { |
| 86: | Assert::that($stopSequences)->values()->isString('each "params.stopSequences" entry must be a string, {type} given.'); |
| 87: | } |
| 88: | |
| 89: | if (null !== $tools) { |
| 90: | Assert::that($tools)->values()->isInstanceOf(Tool::class); |
| 91: | } |
| 92: | |
| 93: | $this->messages = $messages; |
| 94: | $this->systemPrompt = $systemPrompt; |
| 95: | $this->stopSequences = $stopSequences; |
| 96: | $this->metadata = $metadata; |
| 97: | $this->tools = $tools; |
| 98: | |
| 99: | parent::__construct($meta); |
| 100: | } |
| 101: | |
| 102: | |
| 103: | |
| 104: | |
| 105: | #[\Override] |
| 106: | public static function fromArray(array $data): static |
| 107: | { |
| 108: | Assert::that($data)->hasOffset('maxTokens', 'missing the required "maxTokens" key.'); |
| 109: | $maxTokens = $data['maxTokens']; |
| 110: | Assert::that($maxTokens)->isInt('"params.maxTokens" must be an int, {type} given.'); |
| 111: | |
| 112: | Assert::that($data)->hasOffset('messages', 'missing the required "messages" key.'); |
| 113: | Assert::that($data['messages']) |
| 114: | ->isList('"params.messages" must be a list, non-list array given.') |
| 115: | ->values() |
| 116: | ->isArray('each "params.message" must be an object, {type} given.') |
| 117: | ->isMap('each "params.message" must be a string-keyed object.') |
| 118: | ; |
| 119: | $messages = array_map(SamplingMessage::fromArray(...), $data['messages']); |
| 120: | |
| 121: | $includeContext = null; |
| 122: | |
| 123: | if (\array_key_exists('includeContext', $data)) { |
| 124: | $includeContext = EnumValueValidator::parse(IncludeContext::class, $data['includeContext'], '"params.includeContext"'); |
| 125: | } |
| 126: | |
| 127: | $modelPreferences = new ModelPreferences(); |
| 128: | |
| 129: | if (\array_key_exists('modelPreferences', $data)) { |
| 130: | Assert::that($data['modelPreferences']) |
| 131: | ->isArray('"params.modelPreferences" must be an object, {type} given.') |
| 132: | ->isMap('"params.modelPreferences" must be a string-keyed object.') |
| 133: | ; |
| 134: | $modelPreferences = ModelPreferences::fromArray($data['modelPreferences']); |
| 135: | } |
| 136: | |
| 137: | $stopSequences = null; |
| 138: | |
| 139: | if (\array_key_exists('stopSequences', $data)) { |
| 140: | Assert::that($data['stopSequences']) |
| 141: | ->isList('"params.stopSequences" must be a list, non-list array given.') |
| 142: | ->values()->isString('each "params.stopSequences" must be a string, {type} given.') |
| 143: | ; |
| 144: | $stopSequences = $data['stopSequences']; |
| 145: | } |
| 146: | |
| 147: | $systemPrompt = $data['systemPrompt'] ?? null; |
| 148: | Assert::that($systemPrompt)->nullOr()->isString('"params.systemPrompt" must be a string or null, {type} given.'); |
| 149: | |
| 150: | $task = null; |
| 151: | |
| 152: | if (\array_key_exists('task', $data)) { |
| 153: | Assert::that($data['task']) |
| 154: | ->isArray('"params.task" must be an object, {type} given.') |
| 155: | ->isMap('"params.task" must be a string-keyed object.') |
| 156: | ; |
| 157: | $task = TaskMetadata::fromArray($data['task']); |
| 158: | } |
| 159: | |
| 160: | $temperature = $data['temperature'] ?? null; |
| 161: | |
| 162: | if (null !== $temperature) { |
| 163: | $temperature = self::parseNumber($temperature, '"params.temperature" must be a number or null, {type} given.'); |
| 164: | } |
| 165: | |
| 166: | $toolChoice = new ToolChoice(); |
| 167: | |
| 168: | if (\array_key_exists('toolChoice', $data)) { |
| 169: | Assert::that($data['toolChoice']) |
| 170: | ->isArray('"params.toolChoice" must be an object, {type} given.') |
| 171: | ->isMap('"params.toolChoice" must be a string-keyed object.') |
| 172: | ; |
| 173: | $toolChoice = ToolChoice::fromArray($data['toolChoice']); |
| 174: | } |
| 175: | |
| 176: | $tools = null; |
| 177: | |
| 178: | if (\array_key_exists('tools', $data)) { |
| 179: | Assert::that($data['tools']) |
| 180: | ->isList('"params.tools" must be a list, non-list array given.') |
| 181: | ->values() |
| 182: | ->isArray('each "params.tool" must be an object, {type} given.') |
| 183: | ->isMap('each "params.tool" must be a string-keyed object.') |
| 184: | ; |
| 185: | $tools = array_map(Tool::fromArray(...), $data['tools']); |
| 186: | } |
| 187: | |
| 188: | $metadata = null; |
| 189: | |
| 190: | if (\array_key_exists('metadata', $data)) { |
| 191: | Assert::that($data['metadata']) |
| 192: | ->isArray('"params.metadata" must be an object, {type} given.') |
| 193: | ->isMap('"params.metadata" must be a string-keyed object.') |
| 194: | ; |
| 195: | $metadata = $data['metadata']; |
| 196: | } |
| 197: | |
| 198: | $meta = new RequestMetaObject(); |
| 199: | |
| 200: | if (\array_key_exists('_meta', $data)) { |
| 201: | Assert::that($data['_meta']) |
| 202: | ->isArray('"params._meta" must be an object, {type} given.') |
| 203: | ->isMap('"params._meta" must be a string-keyed object.') |
| 204: | ; |
| 205: | $meta = RequestMetaObject::fromArray($data['_meta']); |
| 206: | } |
| 207: | |
| 208: | return new self( |
| 209: | $maxTokens, |
| 210: | $messages, |
| 211: | $includeContext, |
| 212: | $modelPreferences, |
| 213: | $stopSequences, |
| 214: | $systemPrompt, |
| 215: | $task, |
| 216: | $temperature, |
| 217: | $toolChoice, |
| 218: | $tools, |
| 219: | $metadata, |
| 220: | $meta, |
| 221: | ); |
| 222: | } |
| 223: | |
| 224: | #[\Override] |
| 225: | public function toArray(): array |
| 226: | { |
| 227: | $data = [ |
| 228: | ...parent::toArray(), |
| 229: | 'maxTokens' => $this->maxTokens, |
| 230: | 'messages' => array_map(static fn(SamplingMessage $m): array => $m->toArray(), $this->messages), |
| 231: | ]; |
| 232: | |
| 233: | if (null !== $this->includeContext) { |
| 234: | $data['includeContext'] = $this->includeContext->value; |
| 235: | } |
| 236: | |
| 237: | $modelPreferences = $this->modelPreferences->toArray(); |
| 238: | |
| 239: | if ([] !== $modelPreferences) { |
| 240: | $data['modelPreferences'] = $modelPreferences; |
| 241: | } |
| 242: | |
| 243: | if (null !== $this->stopSequences) { |
| 244: | $data['stopSequences'] = $this->stopSequences; |
| 245: | } |
| 246: | |
| 247: | if (null !== $this->systemPrompt) { |
| 248: | $data['systemPrompt'] = $this->systemPrompt; |
| 249: | } |
| 250: | |
| 251: | if (null !== $this->task) { |
| 252: | $data['task'] = $this->task->toArray(); |
| 253: | } |
| 254: | |
| 255: | if (null !== $this->temperature) { |
| 256: | $data['temperature'] = $this->temperature; |
| 257: | } |
| 258: | |
| 259: | $toolChoice = $this->toolChoice->toArray(); |
| 260: | |
| 261: | if ([] !== $toolChoice) { |
| 262: | $data['toolChoice'] = $toolChoice; |
| 263: | } |
| 264: | |
| 265: | if (null !== $this->tools) { |
| 266: | $data['tools'] = array_map(static fn(Tool $t): array => $t->toArray(), $this->tools); |
| 267: | } |
| 268: | |
| 269: | if (null !== $this->metadata) { |
| 270: | $data['metadata'] = $this->metadata; |
| 271: | } |
| 272: | |
| 273: | return $data; |
| 274: | } |
| 275: | |
| 276: | #[\Override] |
| 277: | public function jsonSerialize(): array |
| 278: | { |
| 279: | $data = $this->toArray(); |
| 280: | |
| 281: | $data['messages'] = array_map( |
| 282: | static fn(SamplingMessage $m): array => $m->jsonSerialize(), |
| 283: | $this->messages, |
| 284: | ); |
| 285: | |
| 286: | if ([] !== $this->modelPreferences->toArray()) { |
| 287: | $data['modelPreferences'] = $this->modelPreferences->jsonSerialize(); |
| 288: | } |
| 289: | |
| 290: | if (null !== $this->task) { |
| 291: | $data['task'] = $this->task->jsonSerialize(); |
| 292: | } |
| 293: | |
| 294: | if ([] !== $this->toolChoice->toArray()) { |
| 295: | $data['toolChoice'] = $this->toolChoice->jsonSerialize(); |
| 296: | } |
| 297: | |
| 298: | if ([] === $this->metadata) { |
| 299: | $data['metadata'] = new \stdClass(); |
| 300: | } |
| 301: | |
| 302: | return $data; |
| 303: | } |
| 304: | } |
| 305: | |