Skip to content

Commit 44b0452

Browse files
committed
fix: guard local dispatch against explicit gatewayUrl and expectFinal opts
1 parent 2c6a91f commit 44b0452

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

src/agents/tools/gateway.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,58 @@ describe("callGatewayTool local dispatch (#40237)", () => {
226226

227227
expect(localDispatch).toHaveBeenCalledWith("health", {});
228228
});
229+
230+
it("bypasses local dispatch when explicit gatewayUrl is provided", async () => {
231+
const localDispatch = vi.fn().mockResolvedValue({ local: true });
232+
registerLocalGatewayDispatch(localDispatch);
233+
callGatewayMock.mockResolvedValueOnce({ remote: true });
234+
235+
const result = await callGatewayTool(
236+
"health",
237+
{ gatewayUrl: "ws://127.0.0.1:18789", gatewayToken: "t", timeoutMs: 5000 },
238+
{},
239+
);
240+
241+
expect(localDispatch).not.toHaveBeenCalled();
242+
expect(callGatewayMock).toHaveBeenCalledWith(
243+
expect.objectContaining({
244+
url: "ws://127.0.0.1:18789",
245+
token: "t",
246+
timeoutMs: 5000,
247+
}),
248+
);
249+
expect(result).toEqual({ remote: true });
250+
});
251+
252+
it("bypasses local dispatch when expectFinal is true", async () => {
253+
const localDispatch = vi.fn().mockResolvedValue({ local: true });
254+
registerLocalGatewayDispatch(localDispatch);
255+
callGatewayMock.mockResolvedValueOnce({ final: true });
256+
257+
const result = await callGatewayTool("agent", {}, { message: "hi" }, { expectFinal: true });
258+
259+
expect(localDispatch).not.toHaveBeenCalled();
260+
expect(callGatewayMock).toHaveBeenCalledWith(
261+
expect.objectContaining({
262+
method: "agent",
263+
expectFinal: true,
264+
}),
265+
);
266+
expect(result).toEqual({ final: true });
267+
});
268+
269+
it("respects timeoutMs from opts when falling through to WS", async () => {
270+
const localDispatch = vi.fn().mockResolvedValue({});
271+
registerLocalGatewayDispatch(localDispatch);
272+
callGatewayMock.mockResolvedValueOnce({ ok: true });
273+
274+
await callGatewayTool(
275+
"agent",
276+
{ timeoutMs: 60_000 },
277+
{ message: "test" },
278+
{ expectFinal: true },
279+
);
280+
281+
expect(callGatewayMock).toHaveBeenCalledWith(expect.objectContaining({ timeoutMs: 60_000 }));
282+
});
229283
});

src/agents/tools/gateway.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,19 @@ export async function callGatewayTool<T = Record<string, unknown>>(
146146
) {
147147
// When running inside the gateway process, dispatch directly in-process
148148
// to avoid WS self-contention during active LLM sessions (#40237).
149-
const localResult = tryLocalGatewayDispatch<T>(method, (params ?? {}) as Record<string, unknown>);
150-
if (localResult !== undefined) {
151-
return await localResult;
149+
// Skip local dispatch when:
150+
// - caller explicitly targets a specific gateway URL (may be remote)
151+
// - caller needs expectFinal semantics (completion-waiting not supported by local dispatch)
152+
const hasExplicitUrl = trimToUndefined(opts.gatewayUrl) !== undefined;
153+
const needsExpectFinal = extra?.expectFinal === true;
154+
if (!hasExplicitUrl && !needsExpectFinal) {
155+
const localResult = tryLocalGatewayDispatch<T>(
156+
method,
157+
(params ?? {}) as Record<string, unknown>,
158+
);
159+
if (localResult !== undefined) {
160+
return await localResult;
161+
}
152162
}
153163

154164
const gateway = resolveGatewayOptions(opts);

0 commit comments

Comments
 (0)