
























@@ -1,11 +1,32 @@
1+import type { StreamFn } from "@mariozechner/pi-agent-core";
12import { streamSimple } from "@mariozechner/pi-ai";
23import { describe, expect, it, vi } from "vitest";
4+import * as providerTransportStream from "../provider-transport-stream.js";
35import {
46describeEmbeddedAgentStreamStrategy,
57resolveEmbeddedAgentApiKey,
68resolveEmbeddedAgentStreamFn,
79} from "./stream-resolution.js";
81011+// Wrap createBoundaryAwareStreamFnForModel with a spy that delegates to the
12+// real implementation by default so existing routing tests still observe a
13+// real transport stream; per-test overrideBoundaryAwareStreamFnOnce() injects
14+// a probe stream when a regression test needs to inspect the wrapped
15+// transport's options.
16+vi.mock("../provider-transport-stream.js", async (importOriginal) => {
17+const actual = await importOriginal<typeof providerTransportStream>();
18+return {
19+ ...actual,
20+createBoundaryAwareStreamFnForModel: vi.fn(actual.createBoundaryAwareStreamFnForModel),
21+};
22+});
23+24+const overrideBoundaryAwareStreamFnOnce = (streamFn: StreamFn): void => {
25+vi.mocked(providerTransportStream.createBoundaryAwareStreamFnForModel).mockReturnValueOnce(
26+streamFn,
27+);
28+};
29+930describe("describeEmbeddedAgentStreamStrategy", () => {
1031it("describes provider-owned stream paths explicitly", () => {
1132expect(
@@ -203,4 +224,138 @@ describe("resolveEmbeddedAgentStreamFn", () => {
203224signal: explicitSignal,
204225});
205226});
227+228+it("injects the resolved run api key into the boundary-aware Codex Responses fallback", async () => {
229+const innerStreamFn = vi.fn(async (_model, _context, options) => options);
230+overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
231+const streamFn = resolveEmbeddedAgentStreamFn({
232+currentStreamFn: undefined,
233+shouldUseWebSocketTransport: false,
234+sessionId: "session-1",
235+model: {
236+api: "openai-codex-responses",
237+provider: "openai-codex",
238+id: "gpt-5.5",
239+} as never,
240+resolvedApiKey: "oauth-bearer-token",
241+});
242+243+await expect(
244+streamFn({ provider: "openai-codex", id: "gpt-5.5" } as never, {} as never, {}),
245+).resolves.toMatchObject({ apiKey: "oauth-bearer-token" });
246+expect(innerStreamFn).toHaveBeenCalledTimes(1);
247+});
248+249+it("falls back to authStorage when no resolved api key is available for boundary-aware fallback", async () => {
250+const innerStreamFn = vi.fn(async (_model, _context, options) => options);
251+const authStorage = {
252+getApiKey: vi.fn(async () => "stored-bearer-token"),
253+};
254+overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
255+const streamFn = resolveEmbeddedAgentStreamFn({
256+currentStreamFn: undefined,
257+shouldUseWebSocketTransport: false,
258+sessionId: "session-1",
259+model: {
260+api: "openai-codex-responses",
261+provider: "openai-codex",
262+id: "gpt-5.5",
263+} as never,
264+ authStorage,
265+});
266+267+await expect(
268+streamFn({ provider: "openai-codex", id: "gpt-5.5" } as never, {} as never, {}),
269+).resolves.toMatchObject({ apiKey: "stored-bearer-token" });
270+expect(authStorage.getApiKey).toHaveBeenCalledWith("openai-codex");
271+});
272+273+it("forwards the run abort signal into the boundary-aware fallback when callers omit one", async () => {
274+const innerStreamFn = vi.fn(async (_model, _context, options) => options);
275+const runSignal = new AbortController().signal;
276+overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
277+const streamFn = resolveEmbeddedAgentStreamFn({
278+currentStreamFn: undefined,
279+shouldUseWebSocketTransport: false,
280+sessionId: "session-1",
281+signal: runSignal,
282+model: {
283+api: "openai-codex-responses",
284+provider: "openai-codex",
285+id: "gpt-5.5",
286+} as never,
287+resolvedApiKey: "oauth-bearer-token",
288+});
289+290+await expect(
291+streamFn({ provider: "openai-codex", id: "gpt-5.5" } as never, {} as never, {}),
292+).resolves.toMatchObject({ signal: runSignal, apiKey: "oauth-bearer-token" });
293+});
294+295+it("does not overwrite an explicit signal on the boundary-aware fallback path", async () => {
296+const innerStreamFn = vi.fn(async (_model, _context, options) => options);
297+const runSignal = new AbortController().signal;
298+const explicitSignal = new AbortController().signal;
299+overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
300+const streamFn = resolveEmbeddedAgentStreamFn({
301+currentStreamFn: undefined,
302+shouldUseWebSocketTransport: false,
303+sessionId: "session-1",
304+signal: runSignal,
305+model: {
306+api: "openai-codex-responses",
307+provider: "openai-codex",
308+id: "gpt-5.5",
309+} as never,
310+resolvedApiKey: "oauth-bearer-token",
311+});
312+313+await expect(
314+streamFn({ provider: "openai-codex", id: "gpt-5.5" } as never, {} as never, {
315+signal: explicitSignal,
316+}),
317+).resolves.toMatchObject({ signal: explicitSignal });
318+});
319+320+it("forwards the run signal on the sync boundary-aware fallback path without auth credentials", async () => {
321+const innerStreamFn = vi.fn(async (_model, _context, options) => options);
322+const runSignal = new AbortController().signal;
323+overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
324+const streamFn = resolveEmbeddedAgentStreamFn({
325+currentStreamFn: undefined,
326+shouldUseWebSocketTransport: false,
327+sessionId: "session-1",
328+signal: runSignal,
329+model: {
330+api: "openai-codex-responses",
331+provider: "openai-codex",
332+id: "gpt-5.5",
333+} as never,
334+});
335+336+await expect(
337+streamFn({ provider: "openai-codex", id: "gpt-5.5" } as never, {} as never, {}),
338+).resolves.toMatchObject({ signal: runSignal });
339+});
340+341+it("does not strip cache boundary markers on the boundary-aware fallback path", async () => {
342+const innerStreamFn = vi.fn(async (_model, context, _options) => context);
343+overrideBoundaryAwareStreamFnOnce(innerStreamFn as never);
344+const streamFn = resolveEmbeddedAgentStreamFn({
345+currentStreamFn: undefined,
346+shouldUseWebSocketTransport: false,
347+sessionId: "session-1",
348+model: {
349+api: "openai-codex-responses",
350+provider: "openai-codex",
351+id: "gpt-5.5",
352+} as never,
353+resolvedApiKey: "oauth-bearer-token",
354+});
355+356+const systemPrompt = "intro<<openclaw-cache-boundary>>tail";
357+await expect(
358+streamFn({ provider: "openai-codex", id: "gpt-5.5" } as never, { systemPrompt } as never, {}),
359+).resolves.toMatchObject({ systemPrompt });
360+});
206361});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。