001/*
002 * Copyright 2022-2026 Revetware LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.soklet;
018
019import com.soklet.internal.mcp.protocol.McpServerRuntimeBridge;
020import org.jspecify.annotations.NonNull;
021import org.jspecify.annotations.Nullable;
022
023import javax.annotation.concurrent.ThreadSafe;
024import java.util.Objects;
025
026import static java.util.Objects.requireNonNull;
027
028/**
029 * Immutable server-initiated request carried by an MCP
030 * {@code input_required} result.
031 *
032 * <p>The declaration connects the emitted request to the capability and
033 * registration metadata that permits it. Applications should reuse a
034 * declaration registered through the operation's
035 * {@code inputRequestDeclarations}
036 * configuration. For a durable task, Soklet includes those declarations in
037 * the task origin for the application to persist, then validates that
038 * relationship before it emits the containing task snapshot.
039 *
040 * @author <a href="https://www.revetkn.com">Mark Allen</a>
041 */
042@ThreadSafe
043public final class McpInputRequest {
044        @NonNull
045        private final McpInputRequestDeclaration declaration;
046        @NonNull
047        private final McpJsonObject params;
048
049        /**
050         * Creates an input request from its registered declaration.
051         *
052         * @param declaration registered input-request declaration
053         * @param params method-specific request parameters
054         * @return immutable input request
055         * @throws NullPointerException if an argument is null
056         */
057        @NonNull
058        public static McpInputRequest fromDeclaration(
059                        @NonNull McpInputRequestDeclaration declaration,
060                        @NonNull McpJsonObject params) {
061                return new McpInputRequest(declaration, params);
062        }
063
064        private McpInputRequest(@NonNull McpInputRequestDeclaration declaration,
065                        @NonNull McpJsonObject params) {
066                this.declaration = requireNonNull(declaration);
067                this.params = requireNonNull(params);
068        }
069
070        /** @return registered input-request declaration */
071        @NonNull
072        public McpInputRequestDeclaration getDeclaration() {
073                return this.declaration;
074        }
075
076        /** @return method-specific request parameters */
077        @NonNull
078        public McpJsonObject getParams() {
079                return this.params;
080        }
081
082        /**
083         * Returns the declared client request method.
084         *
085         * @return client JSON-RPC request method
086         */
087        @NonNull
088        public String getJsonRpcMethod() {
089                return this.declaration.getJsonRpcMethod();
090        }
091
092        /**
093         * Reports whether a client response matches this request's declared MCP
094         * input-response union branch.
095         *
096         * <p>This validates the protocol-level response shape for the declaration.
097         * It does not validate accepted elicitation content against this request's
098         * requested schema or against application-specific policy.
099         * Content values may be strings, numbers (including decimals), booleans,
100         * or arrays of strings. Numbers retain their exact {@link java.math.BigDecimal}
101         * values and must satisfy Soklet's production JSON number limits. Matching
102         * does not require a number to be integral, even when the requested schema
103         * declares an integer field; the application must validate that schema.
104         *
105         * @param inputResponse client response to inspect
106         * @return whether the response matches the declared MCP union branch
107         * @throws NullPointerException if {@code inputResponse} is null
108         */
109        @NonNull
110        public Boolean matchesInputResponse(@NonNull McpJsonValue inputResponse) {
111                return McpServerRuntimeBridge.matchesInputResponse(this.declaration,
112                                requireNonNull(inputResponse));
113        }
114
115        void requireValidParams() {
116                McpServerRuntimeBridge.requireValidInputRequestParams(this.declaration,
117                                this.params);
118        }
119
120        /** @return whether this value has the same declaration and parameters */
121        @Override
122        public boolean equals(@Nullable Object other) {
123                if (this == other)
124                        return true;
125                if (!(other instanceof McpInputRequest request))
126                        return false;
127                return this.declaration.equals(request.declaration)
128                                && this.params.equals(request.params);
129        }
130
131        /** @return value-based hash code */
132        @Override
133        public int hashCode() {
134                return Objects.hash(this.declaration, this.params);
135        }
136
137        /** @return rendering that identifies the method but redacts request parameters */
138        @Override
139        @NonNull
140        public final String toString() {
141                return "McpInputRequest{method='%s', params=<redacted>}"
142                                .formatted(getJsonRpcMethod());
143        }
144}