InputRequiredShim
Serves a modern-era handler on a handshake-era connection.
Revision 2026-07-28 removed server-initiated requests: a handler that needs something from the user returns an InputRequiredResult naming it, and the client retries the whole call with the answers. The handshake era has no such retry — it has a channel back to the client instead.
This closes that gap from the server side. When a handler returns an
input-required result on a handshake-era connection, each embedded request
goes out as the real server-to-client request it describes
(elicitation/create, sampling/createMessage, roots/list), and the
handler is re-entered with the answers under the keys it asked for. A handler
written once for the modern era therefore serves both, and cannot tell which
fulfilled it.
The cost is that re-entry is re-execution: the handler runs again from the top each round, so it must re-derive its position from what came back rather than from anything it kept. That is already true of the modern era — the client retries the whole call there — so a portable handler is written that way regardless; it is only new for handlers that until now could rely on ClientGateway::elicit() suspending mid-body and keeping their locals. Those keep working untouched: nothing here runs unless a handler returns an ask.
Runs inside the handler's own fiber, so the wait for each answer is the same suspension ClientGateway already uses, driven by the same transport loop.
Tags
Table of Contents
Constants
- DEFAULT_MAX_ROUNDS : mixed = 8
- Re-entries per originating request. Deliberately below the modern client driver's allowance: this loop holds a live request open.
- DEFAULT_ROUND_TIMEOUT : mixed = 600
- Seconds to wait for one answer. Legs are human-paced, so the protocol's 120s default is wrong here.
Properties
- $logger : LoggerInterface
- $maxRounds : int
- $roundTimeout : int
Methods
- __construct() : mixed
- fulfill() : Response<string|int, mixed>|Error
- Runs a handler to a result the client can be given.
- askOf() : InputRequiredResult|null
- The ask a handler returned, if it returned one.
- collect() : array<string, mixed>
- Sends each embedded request and keeps the answer under the key it was asked under.
- payloadOf() : array<string, mixed>
- The state the handler sealed last round, verified.
- refuseUndeclared() : Error|null
- Refuses an ask the client never said it could answer, the way the modern era does — rather than sending a request that can only come back as an error.
Constants
DEFAULT_MAX_ROUNDS
Re-entries per originating request. Deliberately below the modern client driver's allowance: this loop holds a live request open.
public
mixed
DEFAULT_MAX_ROUNDS
= 8
DEFAULT_ROUND_TIMEOUT
Seconds to wait for one answer. Legs are human-paced, so the protocol's 120s default is wrong here.
public
mixed
DEFAULT_ROUND_TIMEOUT
= 600
Properties
$logger read-only
private
LoggerInterface
$logger
= new NullLogger()
$maxRounds read-only
private
int
$maxRounds
= self::DEFAULT_MAX_ROUNDS
$roundTimeout read-only
private
int
$roundTimeout
= self::DEFAULT_ROUND_TIMEOUT
Methods
__construct()
public
__construct([int $maxRounds = self::DEFAULT_MAX_ROUNDS ][, int $roundTimeout = self::DEFAULT_ROUND_TIMEOUT ][, LoggerInterface $logger = new NullLogger() ]) : mixed
Parameters
- $maxRounds : int = self::DEFAULT_MAX_ROUNDS
- $roundTimeout : int = self::DEFAULT_ROUND_TIMEOUT
- $logger : LoggerInterface = new NullLogger()
fulfill()
Runs a handler to a result the client can be given.
public
fulfill(Response<string|int, mixed>|Error $result, RequestHandlerInterface<string|int, ResultInterface|array<string, mixed>> $handler, Request $request, SessionInterface $session, RequestStateCodec|null $codec) : Response<string|int, mixed>|Error
Returns whatever the handler returned when it asks for nothing, which is every call that is not multi round-trip.
Parameters
- $result : Response<string|int, mixed>|Error
-
what the handler returned on its first entry
- $handler : RequestHandlerInterface<string|int, ResultInterface|array<string, mixed>>
-
the handler that produced it
- $request : Request
- $session : SessionInterface
- $codec : RequestStateCodec|null
Return values
Response<string|int, mixed>|ErroraskOf()
The ask a handler returned, if it returned one.
private
static askOf(Response<string|int, mixed>|Error $result) : InputRequiredResult|null
Parameters
Return values
InputRequiredResult|nullcollect()
Sends each embedded request and keeps the answer under the key it was asked under.
private
collect(InputRequiredResult $ask, SessionInterface $session) : array<string, mixed>
Answers are stored as the raw result arrays InputContext parses, so this needs to know nothing about the kinds it is carrying — which is also why an extension's future kind rides through unchanged.
Parameters
- $ask : InputRequiredResult
- $session : SessionInterface
Return values
array<string, mixed>payloadOf()
The state the handler sealed last round, verified.
private
static payloadOf(InputRequiredResult $ask, RequestStateCodec|null $codec) : array<string, mixed>
Verified rather than trusted even though it never left this process: the handler reads it back through the same accessor either era, so it has to have been through the same check.
Parameters
- $ask : InputRequiredResult
- $codec : RequestStateCodec|null
Tags
Return values
array<string, mixed>refuseUndeclared()
Refuses an ask the client never said it could answer, the way the modern era does — rather than sending a request that can only come back as an error.
private
refuseUndeclared(InputRequiredResult $ask, Request $request, SessionInterface $session) : Error|null
Parameters
- $ask : InputRequiredResult
- $request : Request
- $session : SessionInterface