@@ -14,6 +14,11 @@ import { dirname, join } from "node:path";
1414
1515import type { ReactorEmittedEvent } from "@intx/inference" ;
1616import type { InboundMessage } from "@intx/types/runtime" ;
17+ import {
18+ wire ,
19+ type HarnessRequest ,
20+ type RequestPredicate ,
21+ } from "@intx/inference-testing" ;
1722
1823import { OPERATOR_ORIGINATED_FLAG } from "../src/agent/message-provenance.js" ;
1924import {
@@ -154,6 +159,83 @@ export interface ScriptedReply {
154159 toolCalls ?: { name : string ; args : Record < string , unknown > } [ ] ;
155160}
156161
162+ export interface RuntimeReplyRequest {
163+ body : string ;
164+ request : HarnessRequest ;
165+ }
166+
167+ export interface RuntimeReplies {
168+ /**
169+ * Register one response whose payload is built when its matching request
170+ * arrives. This keeps correlation values discovered during a scenario out
171+ * of eagerly serialized `replyOnce` fixtures.
172+ */
173+ replyOnce : (
174+ provider : Parameters < typeof wire . completeResponse > [ 0 ] ,
175+ predicate : RequestPredicate ,
176+ reply : ( request : RuntimeReplyRequest ) => ScriptedReply | undefined ,
177+ ) => void ;
178+ /**
179+ * Rejects with the first error a reply builder or its clock run raises.
180+ * Race the scenario against it: the harness would otherwise park the error
181+ * for whichever `harness.run` checks next, and a background run that takes
182+ * it first loses it, leaving only the test timeout.
183+ */
184+ failed : Promise < never > ;
185+ }
186+
187+ export function createRuntimeReplies ( session : E2ESession ) : RuntimeReplies {
188+ let fail : ( error : unknown ) => void = ( ) => undefined ;
189+ const failed = new Promise < never > ( ( _ , reject ) => {
190+ fail = reject ;
191+ } ) ;
192+ // A failure that lands after the race settled must not surface as an
193+ // unhandled rejection.
194+ failed . catch ( ( ) => undefined ) ;
195+ const replyOnce : RuntimeReplies [ "replyOnce" ] = ( provider , predicate , reply ) =>
196+ registerRuntimeReply ( session , provider , predicate , reply , fail ) ;
197+ return { replyOnce, failed } ;
198+ }
199+
200+ function registerRuntimeReply (
201+ session : E2ESession ,
202+ provider : Parameters < typeof wire . completeResponse > [ 0 ] ,
203+ predicate : RequestPredicate ,
204+ reply : ( request : RuntimeReplyRequest ) => ScriptedReply | undefined ,
205+ fail : ( error : unknown ) => void ,
206+ ) : void {
207+ const stream = session . harness . scenario . createStream ( ) ;
208+ session . harness . scenario . whenRequestBodyMatches ( ( body , request ) => {
209+ if ( ! predicate ( request ) ) return false ;
210+ let response : ScriptedReply | undefined ;
211+ try {
212+ response = reply ( { body, request } ) ;
213+ } catch ( error ) {
214+ fail ( error ) ;
215+ return false ;
216+ }
217+ if ( response === undefined ) return false ;
218+ const chunks = wire . completeResponse ( provider , {
219+ text : response . text ?? "" ,
220+ ...( response . toolCalls === undefined
221+ ? { }
222+ : {
223+ toolCalls : response . toolCalls . map ( ( call , index ) => ( {
224+ callId : `runtime_call_${ crypto . randomUUID ( ) } _${ index } ` ,
225+ name : call . name ,
226+ argsJSON : JSON . stringify ( call . args ) ,
227+ } ) ) ,
228+ } ) ,
229+ } ) ;
230+ stream . enqueueAll ( chunks , { startAt : session . harness . clock . now ( ) + 1 } ) ;
231+ // A request-time reply can land after runUntilDone's harness.run has
232+ // gone quiescent (e.g. a worker request while the parent waits on the
233+ // wall clock), so drive the clock for the chunks just scheduled.
234+ void session . harness . run ( { wallClockBudgetMs : Infinity } ) . catch ( fail ) ;
235+ return true ;
236+ } , stream ) ;
237+ }
238+
157239/**
158240 * Queue one model response per inference request, in order — the common
159241 * "model calls tools, then answers" spine. Use
0 commit comments