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.client.mcp.v20260728;
018
019
020import java.util.*;
021
022import org.apache.juneau.bean.mcp.v20260728.*;
023import org.apache.juneau.marshall.collections.*;
024import org.apache.juneau.marshall.marshaller.*;
025import static org.apache.juneau.commons.utils.Shorts.*;
026
027/**
028 * Static helper for encoding the client's answers to server→client MCP {@code 2026-07-28} SEP-2322
029 * elicitation requests back into the raw {@code inputResponses} wire shape a resume call expects (MRTR,
030 * the Multi-Round-Trip Requests loop, client side).
031 *
032 * <p>
033 * Paired with {@link ElicitationRequests}, which reads the server's questions out of a paused
034 * {@link McpClient#callRaw} result &mdash; the same {@code ElicitationRequests}/{@code ElicitationResponses}
035 * split the server side already uses (server {@code ElicitationRequests} builds the pause signal; server
036 * {@code ElicitationResponses} reads the resumed answers).
037 *
038 * @since 10.0.0
039 */
040public final class ElicitationResponses {
041
042   private ElicitationResponses() {}
043
044   /**
045    * Encodes a single typed {@link ElicitResult} into the raw {@code inputResponses} payload shape.
046    *
047    * @param id The server-assigned id this answer responds to.  Must not be <jk>null</jk>.
048    * @param result The typed answer.  Must not be <jk>null</jk>.
049    * @return A single-entry map ready to pass to {@link #toInputResponses(Map)}'s multi-answer form, or to hand
050    *    directly to a concrete request bean's {@code setInputResponses(...)} after merging with other answers.
051    * @throws IllegalArgumentException If {@code id} or {@code result} is <jk>null</jk>.
052    */
053   public static Map<String,Object> toInputResponse(String id, ElicitResult result) {
054      reqnn("id", id);
055      reqnn("result", result);
056      return toInputResponses(Map.of(id, result));
057   }
058
059   /**
060    * Encodes several typed {@link ElicitResult}s into the raw {@code inputResponses} payload shape a resume
061    * call's request bean expects (e.g. {@code CallToolRequest.setInputResponses(...)}).
062    *
063    * <p>
064    * Unlike {@code ElicitationRequests.of(Map,Object)} (server side), an empty {@code results} map is accepted
065    * (returning an empty map), not rejected &mdash; deliberately asymmetric, since a caller may legitimately
066    * resume with zero elicitation answers if a round only carried non-elicitation MRTR pauses, whereas building
067    * a signal with zero questions server-side would be a pointless pause.
068    *
069    * @param results Server-assigned-id-keyed typed answers.  Must not be <jk>null</jk>, and no value may be
070    *    <jk>null</jk>.  Output iteration order mirrors this map's order, so a caller wanting deterministic
071    *    ordering should pass a {@link LinkedHashMap}.
072    * @return A keyed map of raw, wire-shaped answers.  Never <jk>null</jk> (empty if {@code results} was empty).
073    * @throws IllegalArgumentException If {@code results} is <jk>null</jk>, or any value in it is <jk>null</jk>.
074    */
075   public static Map<String,Object> toInputResponses(Map<String,ElicitResult> results) {
076      reqnn("results", results);
077      Map<String,Object> out = new LinkedHashMap<>();
078      results.forEach((id, result) -> {
079         reqnn("results[" + id + "]", result);
080         // Pre-marshalled to JsonMap here (rather than left as the typed ElicitResult for McpClient.call's
081         // own toWireParams flattening) so that (a) a null-check on the encoded shape is meaningful even
082         // when this helper is used standalone, outside McpClient.call's flow, and (b) the returned map is
083         // immediately wire-ready for a caller who hands it straight to a concrete request bean's
084         // setInputResponses(...) without ever going through McpClient at all.
085         out.put(id, Json.to(Json.of(result), JsonMap.class));
086      });
087      return out;
088   }
089}