

























@@ -1,15 +1,61 @@
11import type { StreamFn } from "@mariozechner/pi-agent-core";
22import type { Context, Model, SimpleStreamOptions } from "@mariozechner/pi-ai";
3-import { describe, expect, it, vi } from "vitest";
3+import { beforeEach, describe, expect, it, vi } from "vitest";
44import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js";
55import { buildOpenAIProvider } from "./openai-provider.js";
667-const refreshOpenAICodexTokenMock = vi.hoisted(() => vi.fn());
7+const mocks = vi.hoisted(() => ({
8+refreshOpenAICodexToken: vi.fn(),
9+openAIResponsesTransportStreamFn: vi.fn(),
10+}));
811912vi.mock("./openai-codex-provider.runtime.js", () => ({
10-refreshOpenAICodexToken: refreshOpenAICodexTokenMock,
13+refreshOpenAICodexToken: mocks.refreshOpenAICodexToken,
1114}));
121516+vi.mock("openclaw/plugin-sdk/provider-stream-family", async (importOriginal) => {
17+const actual =
18+await importOriginal<typeof import("openclaw/plugin-sdk/provider-stream-family")>();
19+const wrapStreamFn: NonNullable<typeof actual.OPENAI_RESPONSES_STREAM_HOOKS.wrapStreamFn> = (
20+ctx,
21+) => {
22+let nextStreamFn = actual.createOpenAIAttributionHeadersWrapper(ctx.streamFn, {
23+codexNativeTransportStreamFn: mocks.openAIResponsesTransportStreamFn,
24+});
25+26+if (actual.resolveOpenAIFastMode(ctx.extraParams)) {
27+nextStreamFn = actual.createOpenAIFastModeWrapper(nextStreamFn);
28+}
29+30+const serviceTier = actual.resolveOpenAIServiceTier(ctx.extraParams);
31+if (serviceTier) {
32+nextStreamFn = actual.createOpenAIServiceTierWrapper(nextStreamFn, serviceTier);
33+}
34+35+const textVerbosity = actual.resolveOpenAITextVerbosity(ctx.extraParams);
36+if (textVerbosity) {
37+nextStreamFn = actual.createOpenAITextVerbosityWrapper(nextStreamFn, textVerbosity);
38+}
39+40+nextStreamFn = actual.createCodexNativeWebSearchWrapper(nextStreamFn, {
41+config: ctx.config,
42+agentDir: ctx.agentDir,
43+});
44+return actual.createOpenAIResponsesContextManagementWrapper(
45+actual.createOpenAIReasoningCompatibilityWrapper(nextStreamFn),
46+ctx.extraParams,
47+);
48+};
49+50+return {
51+ ...actual,
52+OPENAI_RESPONSES_STREAM_HOOKS: {
53+ ...actual.OPENAI_RESPONSES_STREAM_HOOKS,
54+ wrapStreamFn,
55+},
56+};
57+});
58+1359function runWrappedPayloadCase(params: {
1460wrap: NonNullable<ReturnType<typeof buildOpenAIProvider>["wrapStreamFn"]>;
1561provider: string;
@@ -49,6 +95,13 @@ function runWrappedPayloadCase(params: {
4995}
50965197describe("buildOpenAIProvider", () => {
98+beforeEach(() => {
99+mocks.openAIResponsesTransportStreamFn.mockReset();
100+mocks.openAIResponsesTransportStreamFn.mockImplementation(() => {
101+throw new Error("unexpected native OpenAI Responses transport call");
102+});
103+});
104+52105it("exposes grouped model/auth picker labels for API key setup", () => {
53106const provider = buildOpenAIProvider();
54107const apiKey = provider.auth.find((method) => method.id === "api-key");
@@ -683,6 +736,15 @@ describe("buildOpenAIProvider", () => {
683736if (!wrap) {
684737throw new Error("expected Codex wrapper");
685738}
739+const payload = {
740+store: false,
741+text: { verbosity: "medium" },
742+tools: [{ type: "function", name: "read" }],
743+};
744+mocks.openAIResponsesTransportStreamFn.mockImplementation((model, _context, options) => {
745+options?.onPayload?.(payload, model);
746+return {} as ReturnType<StreamFn>;
747+});
686748const result = runWrappedPayloadCase({
687749 wrap,
688750provider: "openai-codex",
@@ -720,13 +782,10 @@ describe("buildOpenAIProvider", () => {
720782id: "gpt-5.4",
721783baseUrl: "https://chatgpt.com/backend-api",
722784} as Model<"openai-codex-responses">,
723-payload: {
724-store: false,
725-text: { verbosity: "medium" },
726-tools: [{ type: "function", name: "read" }],
727-},
785+ payload,
728786});
729787788+expect(mocks.openAIResponsesTransportStreamFn).toHaveBeenCalledTimes(1);
730789expect(result.payload.store).toBe(false);
731790expect(result.payload.service_tier).toBe("priority");
732791expect(result.payload.text).toEqual({ verbosity: "high" });
@@ -749,8 +808,8 @@ describe("buildOpenAIProvider", () => {
749808expires: Date.now() - 60_000,
750809};
751810752-refreshOpenAICodexTokenMock.mockReset();
753-refreshOpenAICodexTokenMock.mockRejectedValueOnce(
811+mocks.refreshOpenAICodexToken.mockReset();
812+mocks.refreshOpenAICodexToken.mockRejectedValueOnce(
754813new Error("Failed to extract accountId from token"),
755814);
756815此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。