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}