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
019
020import java.util.*;
021
022import org.apache.juneau.marshall.marshaller.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024
025/**
026 * Bean stashed into the {@link org.apache.juneau.commons.inject.BeanStore} passed to a handler on a RESUME call,
027 * carrying the decoded continuation and the client's collected answers.
028 *
029 * <p>
030 * Absent (not stashed) on a first-round call &mdash; a handler that only ever completes in one round never has to
031 * know this type exists. A resumable handler calls {@code ctx.getBean(McpMrtrResumeContext.class)} to detect
032 * resume and retrieve both fields.
033 *
034 * <p>
035 * <b>Per-round arguments are client-controlled; the continuation is not.</b> On RESUME the handler receives its
036 * ordinary {@code arguments} from the follow-up request &mdash; the client re-sends them each round and full
037 * schema validation runs per round, but nothing about the original round's arguments is sealed or verified, so a
038 * client can alter them between rounds and the MRTR layer will accept the resume. Only {@link #continuation()}
039 * (sealed by {@link RequestStateCodec}, AEAD-authenticated) is tamper-evident across rounds. Therefore any value
040 * a handler must trust across rounds must be carried <i>in</i> the continuation, never re-read from the per-round
041 * arguments. The client is expected to re-send the original {@code arguments} on every round; a handler that
042 * needs the exact original arguments should stash them in the continuation rather than rely on the client
043 * echoing them faithfully.
044 *
045 * <p>
046 * <b>Continuation type fidelity (round-trip contract).</b> {@link #continuation()} is whatever
047 * {@link RequestStateCodec#unseal} decoded from the client's echoed {@code requestState}: a <i>generic JSON</i>
048 * value ({@code JsonMap}/{@code JsonList}/boxed primitive/{@code String}), <b>never</b> the original Java type
049 * the handler threw in {@link McpInputRequiredSignal} (see the type-fidelity note on {@link McpRequestState}).
050 * Rather than force handler authors to parse that generic value by hand, {@link #continuationAs(Class)} converts
051 * it back to any Juneau-marshallable target type in one call, so a handler that paused with a bean recovers a
052 * bean of the same shape ergonomically.
053 *
054 * @param continuation The decoded continuation, exactly as {@link RequestStateCodec#unseal} produced it. Can be
055 *    <jk>null</jk> if the handler paused with a <jk>null</jk> continuation. See the round-trip contract above:
056 *    it is generic JSON, not the original Java type &mdash; use {@link #continuationAs(Class)} to recover a typed
057 *    view.
058 * @param inputResponses The client's collected answers, echoed from the follow-up request's
059 *    {@code inputResponses} field. Never <jk>null</jk> (an empty map when the client sent none).
060 */
061public record McpMrtrResumeContext(Object continuation, Map<String,Object> inputResponses) {
062
063   /**
064    * Returns the continuation converted to the given type.
065    *
066    * <p>
067    * Convenience for the round-trip contract documented on this record: because {@link #continuation()} is a
068    * generic JSON value (not the handler's original Java type), a handler that paused with, say, a
069    * {@code ResumeState} bean would otherwise have to reconstruct it manually from a {@code JsonMap}. This
070    * converts the generic value to {@code type} via Juneau's JSON marshaller &mdash; the same mechanism the
071    * codec used to serialize it &mdash; so {@code continuationAs(ResumeState.class)} returns a fully populated
072    * {@code ResumeState}.
073    *
074    * <p>
075    * <b>Failure contract:</b> conversion is best-effort against a generic JSON value. If the continuation's
076    * decoded shape is incompatible with {@code type}, the underlying marshaller throws (a
077    * {@code RuntimeException}); a handler that lets it propagate surfaces the {@code -32603} internal-error
078    * fail-safe from {@code McpRevision#dispatch}. A handler that pauses and resumes with a single, consistent
079    * continuation type never hits this.
080    *
081    * @param <T> The target type.
082    * @param type The target type to convert the continuation to. Must not be <jk>null</jk>.
083    * @return The continuation converted to {@code type}, or <jk>null</jk> if the continuation is <jk>null</jk>.
084    * @throws IllegalArgumentException If {@code type} is <jk>null</jk>.
085    * @throws RuntimeException If the continuation's decoded shape cannot be converted to {@code type}.
086    */
087   public <T> T continuationAs(Class<T> type) {
088      reqnn("type", type);
089      if (continuation == null)
090         return null;
091      return Json.to(Json.of(continuation), type);
092   }
093
094   /**
095    * Returns the continuation as a {@link String}.
096    *
097    * <p>
098    * Convenience for the common case documented on {@link #continuationAs(Class)}: a handler that pauses with
099    * a plain {@code String} continuation can call this instead of spelling out {@code continuationAs(String.class)}
100    * at the headline pause/resume call site.
101    *
102    * <p>
103    * <b>Conversion is not lenient.</b> {@code continuationAs(String.class)} routes through the JSON marshaller,
104    * which parses {@code String} the same as any other target type &mdash; it requires a quoted JSON string
105    * literal, so a continuation that decoded to anything else (a bare number, a {@code JsonMap}, a
106    * {@code JsonList}, a boolean) throws below rather than being stringified. Only a continuation whose
107    * decoded shape is already a {@link String} converts successfully.
108    *
109    * @return The continuation as a {@link String}, or <jk>null</jk> if the continuation is <jk>null</jk>.
110    * @throws RuntimeException If the continuation's decoded shape is not already a {@link String}.
111    */
112   public String continuationAsString() {
113      return continuationAs(String.class);
114   }
115}