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 org.jspecify.annotations.NonNull;
020import org.jspecify.annotations.Nullable;
021
022import javax.annotation.concurrent.NotThreadSafe;
023import javax.annotation.concurrent.ThreadSafe;
024
025import java.util.Objects;
026
027import static com.soklet.internal.mcp.protocol.McpApplicationMetadata.requireApplicationMetadata;
028import static java.util.Objects.requireNonNull;
029
030/**
031 * Immutable completed result returned by an MCP operation handler.
032 *
033 * @author <a href="https://www.revetkn.com">Mark Allen</a>
034 */
035@ThreadSafe
036public final class McpCompleteResult implements McpOperationResult {
037        @NonNull
038        private final McpCompletePayload payload;
039        @NonNull
040        private final McpJsonObject metadata;
041
042        /**
043         * Creates a successful prose tool result.
044         *
045         * @param text prose text
046         * @return complete tool result
047         */
048        @NonNull
049        public static McpCompleteResult fromToolText(@NonNull String text) {
050                return fromToolOutput(McpToolOutput.fromText(text));
051        }
052
053        /**
054         * Creates a successful structured tool result.
055         *
056         * @param structuredContent structured JSON value
057         * @return complete tool result
058         */
059        @NonNull
060        public static McpCompleteResult fromToolStructuredContent(
061                        @NonNull McpJsonValue structuredContent) {
062                return fromToolOutput(
063                                McpToolOutput.fromStructuredContent(structuredContent));
064        }
065
066        /**
067         * Creates an application-level error tool result.
068         *
069         * @param text safe client-visible error text
070         * @return complete error tool result
071         */
072        @NonNull
073        public static McpCompleteResult fromToolErrorText(@NonNull String text) {
074                return fromToolOutput(McpToolOutput.fromErrorText(text));
075        }
076
077        /**
078         * Wraps explicit tool output.
079         *
080         * @param toolOutput tool output
081         * @return complete tool result
082         */
083        @NonNull
084        public static McpCompleteResult fromToolOutput(
085                        @NonNull McpToolOutput toolOutput) {
086                return new McpCompleteResult(toolOutput, McpJsonObject.emptyInstance());
087        }
088
089        /**
090         * Wraps explicit prompt output.
091         *
092         * @param promptOutput prompt output
093         * @return complete prompt result
094         */
095        @NonNull
096        public static McpCompleteResult fromPromptOutput(
097                        @NonNull McpPromptOutput promptOutput) {
098                return new McpCompleteResult(promptOutput, McpJsonObject.emptyInstance());
099        }
100
101        /**
102         * Wraps explicit resource output.
103         *
104         * @param resourceOutput resource output
105         * @return complete resource result
106         */
107        @NonNull
108        public static McpCompleteResult fromResourceOutput(
109                        @NonNull McpResourceOutput resourceOutput) {
110                return new McpCompleteResult(resourceOutput, McpJsonObject.emptyInstance());
111        }
112
113        /**
114         * Starts a builder with tool output and empty result metadata.
115         *
116         * @param toolOutput complete tool output
117         * @return mutable result builder
118         */
119        @NonNull
120        public static Builder withToolOutput(@NonNull McpToolOutput toolOutput) {
121                return new Builder(toolOutput);
122        }
123
124        /**
125         * Starts a builder with prompt output and empty result metadata.
126         *
127         * @param promptOutput complete prompt output
128         * @return mutable result builder
129         */
130        @NonNull
131        public static Builder withPromptOutput(@NonNull McpPromptOutput promptOutput) {
132                return new Builder(promptOutput);
133        }
134
135        /**
136         * Starts a builder with resource output and empty result metadata.
137         *
138         * @param resourceOutput complete resource output
139         * @return mutable result builder
140         */
141        @NonNull
142        public static Builder withResourceOutput(@NonNull McpResourceOutput resourceOutput) {
143                return new Builder(resourceOutput);
144        }
145
146        private McpCompleteResult(@NonNull McpCompletePayload payload,
147                        @NonNull McpJsonObject metadata) {
148                this.payload = requireNonNull(payload);
149                this.metadata = requireApplicationMetadata(metadata);
150        }
151
152        /**
153         * Starts an independent builder preserving every field of this result.
154         *
155         * @return mutable result builder with the same payload and metadata
156         */
157        @NonNull
158        public Builder toBuilder() {
159                return new Builder(this.payload).metadata(this.metadata);
160        }
161
162        /** @return operation-specific complete payload */
163        @NonNull
164        public McpCompletePayload getPayload() {
165                return this.payload;
166        }
167
168        /** @return immutable protocol extension metadata */
169        @NonNull
170        public McpJsonObject getMetadata() {
171                return this.metadata;
172        }
173
174        /** @return whether the payload and metadata are structurally equal */
175        @Override
176        public boolean equals(@Nullable Object other) {
177                if (this == other)
178                        return true;
179                if (!(other instanceof McpCompleteResult result))
180                        return false;
181                return this.payload.equals(result.payload)
182                                && this.metadata.equals(result.metadata);
183        }
184
185        /** @return structural payload-and-metadata hash code */
186        @Override
187        public int hashCode() {
188                return Objects.hash(this.payload, this.metadata);
189        }
190
191        /**
192         * Mutable builder for an immutable complete result.
193         *
194         * @author <a href="https://www.revetkn.com">Mark Allen</a>
195         */
196        @NotThreadSafe
197        public static final class Builder {
198                @NonNull
199                private McpCompletePayload payload;
200                @NonNull
201                private McpJsonObject metadata = McpJsonObject.emptyInstance();
202
203                private Builder(@NonNull McpCompletePayload payload) {
204                        this.payload = requireNonNull(payload);
205                }
206
207                /**
208                 * Replaces the operation-specific payload.
209                 *
210                 * @param payload complete tool, prompt, or resource output
211                 * @return this builder
212                 */
213                @NonNull
214                public Builder payload(@NonNull McpCompletePayload payload) {
215                        this.payload = requireNonNull(payload);
216                        return this;
217                }
218
219                /**
220                 * Replaces application-owned result metadata.
221                 * Metadata is out-of-band data, not a confidential channel: hosts may log
222                 * or forward it. Application authorization and redaction remain necessary.
223                 *
224                 * @param metadata immutable metadata without reserved MCP keys
225                 * @return this builder
226                 * @throws NullPointerException if {@code metadata} is null
227                 * @throws IllegalArgumentException if metadata uses a reserved MCP key
228                 */
229                @NonNull
230                public Builder metadata(@NonNull McpJsonObject metadata) {
231                        this.metadata = requireApplicationMetadata(metadata);
232                        return this;
233                }
234
235                /** @return immutable snapshot of the current payload and metadata */
236                @NonNull
237                public McpCompleteResult build() {
238                        return new McpCompleteResult(this.payload, this.metadata);
239                }
240        }
241}