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\Extension\Tasks\Schema\RequestParams;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Elicitation\ElicitResult;
18: use Nexus\Mcp\Core\Schema\MetaObject;
19: use Nexus\Mcp\Core\Schema\MetaObject\RequestMetaObject;
20: use Nexus\Mcp\Core\Schema\RequestParams;
21: use Nexus\Mcp\Core\Schema\Result\InputResponse;
22:
23: /**
24: * Parameters for a `tasks/update` request.
25: *
26: * @extends RequestParams<array{
27: * _meta: template-type<RequestMetaObject, MetaObject, 'T'>,
28: * taskId: non-empty-string,
29: * inputResponses: array<int|non-empty-string, array<string, mixed>>,
30: * }>
31: *
32: * @see https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md
33: */
34: final readonly class UpdateTaskRequestParams extends RequestParams
35: {
36: /**
37: * @param non-empty-string $taskId
38: * @param array<int|non-empty-string, array<string, mixed>|InputResponse> $inputResponses
39: */
40: public function __construct(
41: public string $taskId,
42: public array $inputResponses,
43: RequestMetaObject $meta,
44: ) {
45: Assert::that($inputResponses)->keys()->isIntOrNonEmptyString('each "params.inputResponses" key must be an int or non-empty string.');
46:
47: foreach ($inputResponses as $response) {
48: if (! $response instanceof InputResponse) {
49: Assert::that($response)
50: ->isArray('each "params.inputResponses" entry must be an InputResponse or an object, {type} given.')
51: ->isMap('each "params.inputResponses" entry must be an InputResponse or a string-keyed object.')
52: ;
53: }
54: }
55:
56: parent::__construct(meta: $meta);
57: }
58:
59: #[\Override]
60: public static function fromArray(array $data): static
61: {
62: Assert::that($data)->hasOffset('taskId', '"params" is missing the required "taskId" key.');
63: $taskId = $data['taskId'];
64: Assert::that($taskId)->isNonEmptyString('"params.taskId" must be a non-empty string, {type} given.');
65:
66: Assert::that($data)->hasOffset('inputResponses', '"params" is missing the required "inputResponses" key.');
67: Assert::that($data['inputResponses'])
68: ->isArray('"params.inputResponses" must be an object, {type} given.')
69: ->keys()->isIntOrNonEmptyString('each "params.inputResponses" key must be an int or non-empty string.')
70: ;
71: Assert::that($data['inputResponses'])
72: ->values()
73: ->isArray('each "params.inputResponses" entry must be an object, {type} given.')
74: ->isMap('each "params.inputResponses" entry must be a string-keyed object.')
75: ;
76: $inputResponses = array_map(
77: static function (array $response): array|InputResponse {
78: try {
79: return ElicitResult::fromArray($response);
80: } catch (\InvalidArgumentException) {
81: return $response;
82: }
83: },
84: $data['inputResponses'],
85: );
86:
87: Assert::that($data)->hasOffset('_meta', '"params" is missing the required "_meta" key.');
88: Assert::that($data['_meta'])
89: ->isArray('"params._meta" must be an object, {type} given.')
90: ->not()->isNonEmptyList('"params._meta" must be a string-keyed object.')
91: ;
92: $meta = RequestMetaObject::fromArray($data['_meta']);
93:
94: return new self(taskId: $taskId, inputResponses: $inputResponses, meta: $meta);
95: }
96:
97: #[\Override]
98: public function toArray(): array
99: {
100: return [
101: '_meta' => $this->meta->toArray(),
102: 'taskId' => $this->taskId,
103: 'inputResponses' => array_map(
104: static fn(array|InputResponse $response): array => $response instanceof InputResponse ? $response->toArray() : $response,
105: $this->inputResponses,
106: ),
107: ];
108: }
109:
110: #[\Override]
111: public function jsonSerialize(): array
112: {
113: $data = parent::jsonSerialize();
114: $data['inputResponses'] = array_map(
115: static fn(array|InputResponse $response): array|\stdClass => $response instanceof InputResponse ? $response->jsonSerialize() : $response,
116: $this->inputResponses,
117: );
118:
119: // The slot is required, so an empty or digit-named map must still encode as an object.
120: if (array_is_list($this->inputResponses)) {
121: $data['inputResponses'] = (object) $data['inputResponses'];
122: }
123:
124: return $data;
125: }
126: }
127: