001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.mcp.v20260728;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023import org.apache.juneau.bean.mcp.v20260728.*;
024import org.apache.juneau.marshall.collections.*;
025import org.apache.juneau.marshall.marshaller.*;
026
027/**
028 * Static helper that builds an {@link McpInputRequiredSignal} from one or more typed {@link ElicitRequest}s (MCP
029 * {@code 2026-07-28} SEP-2322 elicitation, riding the Multi-Round-Trip Requests loop).
030 *
031 * <p>
032 * {@link McpInputRequiredSignal}'s constructor takes {@code Map<String,Object>} whose values must be {@code Map}
033 * at runtime (see its own Javadoc); this helper serializes each {@link ElicitRequest} to that shape via Juneau's
034 * JSON marshaller (round-tripped through {@link JsonMap}, the same technique
035 * {@link McpMrtrResumeContext#continuationAs(Class)} already demonstrates for the continuation side) so a
036 * handler never hand-builds the raw map itself. Pure marshalling: no dispatcher-visible state, no new capability
037 * gate — the existing elicitation-specific gate in {@code McpRevision#pause} already covers every signal this
038 * helper produces.
039 */
040public final class ElicitationRequests {
041
042   private ElicitationRequests() {}
043
044   /**
045    * Builds a single-question {@link McpInputRequiredSignal}.
046    *
047    * @param id The server-assigned id for this question.  Must not be <jk>null</jk>.
048    * @param request The typed elicitation request.  Must not be <jk>null</jk>.
049    * @param continuation Handler-opaque continuation value.  Can be <jk>null</jk> if the handler needs no
050    *    state carried between rounds.
051    * @return A new signal ready to be thrown.
052    * @throws IllegalArgumentException If {@code id} or {@code request} is <jk>null</jk>.
053    */
054   public static McpInputRequiredSignal of(String id, ElicitRequest request, Object continuation) {
055      reqnn("id", id);
056      reqnn("request", request);
057      return of(Map.of(id, request), continuation);
058   }
059
060   /**
061    * Builds a multi-question {@link McpInputRequiredSignal} (MRTR's multi-request-per-round case: several
062    * elicitations paused on together, resumed together).
063    *
064    * <p>
065    * Unlike {@code ElicitationResponses.toInputResponses(Map)} (client side), an empty {@code requests} map is
066    * rejected, not accepted &mdash; deliberately asymmetric, since a signal built with zero questions would be
067    * a pointless pause, whereas a caller resuming may legitimately have zero elicitation answers on hand if
068    * the round it is resuming only carried non-elicitation MRTR pauses.
069    *
070    * @param requests Server-assigned-id-keyed typed elicitation requests.  Must not be <jk>null</jk> or empty,
071    *    and no value may be <jk>null</jk>.  Output iteration order mirrors this map's order, so a caller
072    *    wanting deterministic ordering should pass a {@link LinkedHashMap}.
073    * @param continuation Handler-opaque continuation value.  Can be <jk>null</jk>.
074    * @return A new signal ready to be thrown.
075    * @throws IllegalArgumentException If {@code requests} is <jk>null</jk> or empty, or any value in it is
076    *    <jk>null</jk>.
077    */
078   public static McpInputRequiredSignal of(Map<String,ElicitRequest> requests, Object continuation) {
079      reqnn("requests", requests);
080      if (requests.isEmpty())
081         throw iaex("requests must not be empty");
082      Map<String,Object> raw = new LinkedHashMap<>();
083      requests.forEach((id, request) -> {
084         reqnn("requests[" + id + "]", request);
085         raw.put(id, Json.to(Json.of(request), JsonMap.class));
086      });
087      return new McpInputRequiredSignal(raw, continuation);
088   }
089}