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
025/**
026 * Bounded capture controls for one simulated MCP response.
027 *
028 * @author <a href="https://www.revetkn.com">Mark Allen</a>
029 */
030@ThreadSafe
031public final class McpSimulationOptions {
032        private static final int DEFAULT_STREAM_ITEM_QUEUE_CAPACITY = 128;
033        private static final int DEFAULT_CAPTURED_SIZE_IN_BYTES = 10 * 1024 * 1024;
034        @NonNull
035        private static final McpSimulationOptions DEFAULT_INSTANCE = builder().build();
036        private final int streamItemQueueCapacity;
037        private final int maximumCapturedSizeInBytes;
038
039        private McpSimulationOptions(@NonNull Builder builder) {
040                this.streamItemQueueCapacity = requirePositive(
041                                builder.streamItemQueueCapacity, "streamItemQueueCapacity");
042                this.maximumCapturedSizeInBytes = requirePositive(
043                                builder.maximumCapturedSizeInBytes,
044                                "maximumCapturedSizeInBytes");
045        }
046
047        /**
048         * @return the shared options instance with a 128-item queue and a
049         * 10,485,760-byte cumulative capture bound
050         */
051        @NonNull
052        public static McpSimulationOptions defaultInstance() {
053                return DEFAULT_INSTANCE;
054        }
055
056        /** @return a new mutable builder initialized to the documented defaults */
057        @NonNull
058        public static Builder builder() {
059                return new Builder();
060        }
061
062        /** @return the maximum number of pending captured SSE items */
063        @NonNull
064        public Integer getStreamItemQueueCapacity() {
065                return this.streamItemQueueCapacity;
066        }
067
068        /**
069         * @return the cumulative byte bound across captured response or SSE bytes;
070         * consuming an SSE item does not refund bytes
071         */
072        @NonNull
073        public Integer getMaximumCapturedSizeInBytes() {
074                return this.maximumCapturedSizeInBytes;
075        }
076
077        private static int requirePositive(int value, @NonNull String name) {
078                if (value <= 0)
079                        throw new IllegalArgumentException(name + " must be positive");
080                return value;
081        }
082
083        /**
084         * Mutable builder for {@link McpSimulationOptions}; not thread-safe.
085         *
086         * @author <a href="https://www.revetkn.com">Mark Allen</a>
087         */
088        @NotThreadSafe
089        public static final class Builder {
090                private int streamItemQueueCapacity =
091                                DEFAULT_STREAM_ITEM_QUEUE_CAPACITY;
092                private int maximumCapturedSizeInBytes = DEFAULT_CAPTURED_SIZE_IN_BYTES;
093
094                private Builder() {
095                }
096
097                /**
098                 * Sets the maximum pending SSE-item count. The default is {@code 128}.
099                 *
100                 * @param streamItemQueueCapacity positive queue capacity, or {@code null}
101                 *                                to restore the default
102                 * @return this builder
103                 */
104                @NonNull
105                public Builder streamItemQueueCapacity(
106                                @Nullable Integer streamItemQueueCapacity) {
107                        this.streamItemQueueCapacity = streamItemQueueCapacity == null
108                                        ? DEFAULT_STREAM_ITEM_QUEUE_CAPACITY : streamItemQueueCapacity;
109                        return this;
110                }
111
112                /**
113                 * Sets the cumulative captured-byte bound. Consuming items never refunds
114                 * this budget. The default is 10,485,760 bytes.
115                 *
116                 * @param maximumCapturedSizeInBytes positive byte limit, or {@code null}
117                 *                                   to restore the default
118                 * @return this builder
119                 */
120                @NonNull
121                public Builder maximumCapturedSizeInBytes(
122                                @Nullable Integer maximumCapturedSizeInBytes) {
123                        this.maximumCapturedSizeInBytes = maximumCapturedSizeInBytes == null
124                                        ? DEFAULT_CAPTURED_SIZE_IN_BYTES : maximumCapturedSizeInBytes;
125                        return this;
126                }
127
128                /**
129                 * Validates positive bounds and creates an immutable options value.
130                 *
131                 * @return immutable options
132                 * @throws IllegalArgumentException if either configured bound is not positive
133                 */
134                @NonNull
135                public McpSimulationOptions build() {
136                        return new McpSimulationOptions(this);
137                }
138        }
139}