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;
024import java.util.List;
025import java.util.Objects;
026import java.util.Optional;
027
028/**
029 * Immutable output of one completed MCP prompt request.
030 *
031 * @author <a href="https://www.revetkn.com">Mark Allen</a>
032 */
033@ThreadSafe
034public final class McpPromptOutput implements McpCompletePayload {
035        @Nullable
036        private final String description;
037        @NonNull
038        private final List<@NonNull McpPromptMessage> messages;
039
040        /** @return an empty prompt-output builder */
041        @NonNull
042        public static Builder builder() {
043                return new Builder();
044        }
045
046        /**
047         * Creates prompt output from messages.
048         *
049         * @param messages prompt messages
050         * @return immutable prompt output
051         */
052        @NonNull
053        public static McpPromptOutput fromMessages(
054                        @NonNull McpPromptMessage @NonNull... messages) {
055                return builder().messages(List.of(messages)).build();
056        }
057
058        private McpPromptOutput(@NonNull Builder builder) {
059                this.description = builder.description;
060                this.messages = List.copyOf(builder.messages);
061        }
062
063        /** @return prompt description, if supplied */
064        @NonNull
065        public Optional<@NonNull String> getDescription() {
066                return Optional.ofNullable(this.description);
067        }
068
069        /** @return immutable messages in insertion order */
070        @NonNull
071        public List<@NonNull McpPromptMessage> getMessages() {
072                return this.messages;
073        }
074
075        /** @return whether the description and messages are structurally equal */
076        @Override
077        public boolean equals(@Nullable Object other) {
078                if (this == other)
079                        return true;
080                if (!(other instanceof McpPromptOutput output))
081                        return false;
082                return Objects.equals(this.description, output.description)
083                                && this.messages.equals(output.messages);
084        }
085
086        /** @return structural prompt-output hash code */
087        @Override
088        public int hashCode() {
089                return Objects.hash(this.description, this.messages);
090        }
091
092        /**
093         * Mutable builder for immutable {@link McpPromptOutput}.
094         *
095         * @author <a href="https://www.revetkn.com">Mark Allen</a>
096         */
097        @NotThreadSafe
098        public static final class Builder {
099                @Nullable
100                private String description;
101                @NonNull
102                private List<@NonNull McpPromptMessage> messages =
103                                List.of();
104
105                private Builder() {
106                }
107
108                /** @param description exact prompt description, without normalization
109                 * @return this builder
110                 * @throws IllegalArgumentException if the description contains an unpaired surrogate */
111                @NonNull
112                public Builder description(@NonNull String description) {
113                        this.description = McpContentValueSupport.requireWellFormedString(description);
114                        return this;
115                }
116
117                /**
118                 * Replaces prompt messages in supplied order.
119                 * Null or empty clears the property. The complete list is validated and
120                 * snapshotted before replacing the prior value.
121                 *
122                 * @param messages prompt messages, or null to clear
123                 * @return this builder
124                 * @throws NullPointerException if a list element is null
125                 */
126                @NonNull
127                public Builder messages(
128                                @Nullable List<@NonNull McpPromptMessage> messages) {
129                        this.messages = messages == null ? List.of()
130                                        : List.copyOf(messages);
131                        return this;
132                }
133
134                /** @return immutable prompt output */
135                @NonNull
136                public McpPromptOutput build() {
137                        return new McpPromptOutput(this);
138                }
139        }
140}