Wrap Execution
Execution intercepts receive the real request
and a continuation representing the rest
of the call path. The example’s execution group registers a tool wrapper, a unary LLM
wrapper, and an LLM stream wrapper at the configured priority.
Tool and Unary Results
The tool wrapper calls next once, awaits the downstream ToolExecutionResult, and
converts it into a
ToolExecutionInterceptOutcome with the
example’s pending mark. The
conversion preserves both the application result and any opaque annotation. Relay
owns the pending mark: it is emitted in the managed lifecycle and does not appear in the
application-visible result.
The unary LLM wrapper has a deliberately smaller contract and returns provider-response
JSON. LLM pending marks, annotations, and optimization contributions belong to the
request-intercept outcome shown in Control Requests,
not the execution result.
If downstream returns ToolExecutionResult::annotated(json!({"answer": 42}), json!({"source": "provider"})), the application receives that same result object and
reads tool_result.result["answer"]. Relay separately emits
example.native.tool_execution under the managed tool call. That separation is why the
callback returns an outcome instead of returning a plain JSON value.
The continuation is reusable. A deliberate configuration or request flag in the example
can invoke unary next twice concurrently, await both responses, and select one. This
demonstrates the API while preserving the cost of repetition. A repeated tool can perform
its side effect twice, and a repeated model call can incur provider cost twice. Production
plugins need an idempotency
or charging policy before they use this pattern.
Cloning next creates another handle to the same downstream continuation. tokio::join!
polls both calls concurrently; it does not make either provider operation free or
idempotent. The wrapper propagates a failure from either call and returns the first
response only after both calls succeed. The request flag makes repeated execution
observable and opt-in for this example.
Calling next zero times is also valid and replaces downstream execution. Conditional
guardrails are clearer for ordinary allow-or-block policy; zero-call execution wrappers
are useful when the plugin intentionally synthesizes a complete result.
Transform Streams Lazily
The stream intercept asks the continuation for a downstream stream and maps chunks as the consumer polls them. It does not collect the stream into an array. Each transformed chunk preserves the downstream JSON fields, and terminal error or cancellation ends the output promptly.
The outer await obtains the downstream stream. The map closure runs later, once per
polled chunk, and preserves downstream errors through chunk.map. There is no collection
step and therefore no requirement to hold the entire response in memory.
The native raw queue is bounded, but typed SDK users see an asynchronous stream facade. The SDK handles the host push protocol; the plugin still must avoid producing unbounded work ahead of demand and must release per-invocation state after clean completion, error, or cancellation.
Verify Execution Behavior
Use the following procedure to verify unary, repeated, and streaming continuation behavior:
- Activate the example with
execution.enabled = true, priority 30, andemit_pending_marks = true. - Execute a tool and a unary model call. Confirm each downstream callback runs once, the application receives only its expected result, and an additional pending mark is emitted under the managed call scope.
- Enable the example’s repeated-continuation input and confirm two downstream unary invocations can overlap. Verify the selected result and accounting explicitly.
- Consume a three-chunk LLM stream one item at a time. Confirm the first transformed chunk arrives before the downstream stream completes.
- Drop a second stream after its first chunk. Confirm that downstream production and plugin work stop cooperatively.
- Clear the component and repeat the calls to prove that no wrapper or pending mark remains registered.
Success means unary and stream continuations preserve scope, errors, and cancellation, while Relay-owned tool marks and LLM request accounting remain separate from application results.