mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-20 04:24:45 +00:00
* fix(codex): enable native code mode in harness * test(codex): update code mode prompt snapshots * test(codex): align code mode thread config expectations * chore(protocol): refresh generated Swift agent params * fix(codex): enable code-mode-only harness threads * test(discord): fix test mock type assertions * test: fix remaining test type assertions * test(matrix): guard avatar loader test callback
206 lines
6.7 KiB
TypeScript
206 lines
6.7 KiB
TypeScript
import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildThreadResumeParams,
|
|
buildThreadStartParams,
|
|
resolveReasoningEffort,
|
|
} from "./thread-lifecycle.js";
|
|
|
|
function createAttemptParams(params: {
|
|
provider: string;
|
|
authProfileId?: string;
|
|
authProfileProvider?: string;
|
|
authProfileProviders?: Record<string, string>;
|
|
}): EmbeddedRunAttemptParams {
|
|
const authProfileProviders =
|
|
params.authProfileProviders ??
|
|
(params.authProfileId
|
|
? { [params.authProfileId]: params.authProfileProvider ?? "openai-codex" }
|
|
: {});
|
|
return {
|
|
provider: params.provider,
|
|
modelId: "gpt-5.4",
|
|
authProfileId: params.authProfileId,
|
|
authProfileStore: {
|
|
version: 1,
|
|
profiles: Object.fromEntries(
|
|
Object.entries(authProfileProviders).map(([profileId, provider]) => [
|
|
profileId,
|
|
{
|
|
type: "oauth" as const,
|
|
provider,
|
|
access: "access-token",
|
|
refresh: "refresh-token",
|
|
expires: Date.now() + 60_000,
|
|
},
|
|
]),
|
|
),
|
|
},
|
|
} as EmbeddedRunAttemptParams;
|
|
}
|
|
|
|
function createAppServerOptions() {
|
|
return {
|
|
approvalPolicy: "on-request",
|
|
approvalsReviewer: "user",
|
|
sandbox: "workspace-write",
|
|
} as const;
|
|
}
|
|
|
|
describe("Codex app-server native code mode config", () => {
|
|
it("enables Codex code-mode-only on thread/start without clobbering other config", () => {
|
|
const request = buildThreadStartParams(createAttemptParams({ provider: "openai" }), {
|
|
cwd: "/repo",
|
|
dynamicTools: [],
|
|
appServer: createAppServerOptions() as never,
|
|
developerInstructions: "test instructions",
|
|
config: {
|
|
"features.codex_hooks": true,
|
|
apps: { _default: { enabled: false } },
|
|
},
|
|
});
|
|
|
|
expect(request.config).toEqual({
|
|
"features.codex_hooks": true,
|
|
apps: { _default: { enabled: false } },
|
|
"features.code_mode": true,
|
|
"features.code_mode_only": true,
|
|
});
|
|
});
|
|
|
|
it("enables Codex code-mode-only on thread/resume", () => {
|
|
const request = buildThreadResumeParams(createAttemptParams({ provider: "openai" }), {
|
|
threadId: "thread-1",
|
|
appServer: createAppServerOptions() as never,
|
|
developerInstructions: "test instructions",
|
|
});
|
|
|
|
expect(request.config).toEqual({
|
|
"features.code_mode": true,
|
|
"features.code_mode_only": true,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Codex app-server model provider selection", () => {
|
|
it.each(["openai", "openai-codex"])(
|
|
"omits public %s modelProvider when forwarding native Codex auth on thread/start",
|
|
(provider) => {
|
|
const request = buildThreadStartParams(
|
|
createAttemptParams({ provider, authProfileId: "work" }),
|
|
{
|
|
cwd: "/repo",
|
|
dynamicTools: [],
|
|
appServer: createAppServerOptions() as never,
|
|
developerInstructions: "test instructions",
|
|
},
|
|
);
|
|
|
|
expect(request).not.toHaveProperty("modelProvider");
|
|
},
|
|
);
|
|
|
|
it("uses the bound native Codex auth profile when deciding thread/resume modelProvider", () => {
|
|
const request = buildThreadResumeParams(
|
|
createAttemptParams({
|
|
provider: "openai",
|
|
authProfileProviders: { bound: "openai-codex" },
|
|
}),
|
|
{
|
|
threadId: "thread-1",
|
|
authProfileId: "bound",
|
|
appServer: createAppServerOptions() as never,
|
|
developerInstructions: "test instructions",
|
|
},
|
|
);
|
|
|
|
expect(request).not.toHaveProperty("modelProvider");
|
|
});
|
|
|
|
it("does not infer native Codex auth from the profile id prefix", () => {
|
|
const request = buildThreadStartParams(
|
|
createAttemptParams({
|
|
provider: "openai",
|
|
authProfileId: "openai-codex:work",
|
|
authProfileProvider: "openai",
|
|
}),
|
|
{
|
|
cwd: "/repo",
|
|
dynamicTools: [],
|
|
appServer: createAppServerOptions() as never,
|
|
developerInstructions: "test instructions",
|
|
},
|
|
);
|
|
|
|
expect(request).toMatchObject({ modelProvider: "openai" });
|
|
});
|
|
|
|
it("keeps public OpenAI modelProvider when no native Codex auth profile is selected", () => {
|
|
const request = buildThreadStartParams(createAttemptParams({ provider: "openai" }), {
|
|
cwd: "/repo",
|
|
dynamicTools: [],
|
|
appServer: createAppServerOptions() as never,
|
|
developerInstructions: "test instructions",
|
|
});
|
|
|
|
expect(request).toMatchObject({ modelProvider: "openai" });
|
|
});
|
|
});
|
|
|
|
describe("resolveReasoningEffort (#71946)", () => {
|
|
describe("modern Codex models (none/low/medium/high/xhigh enum)", () => {
|
|
it.each(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.2"] as const)(
|
|
"translates 'minimal' -> 'low' for %s so the first request is accepted",
|
|
(modelId) => {
|
|
expect(resolveReasoningEffort("minimal", modelId)).toBe("low");
|
|
},
|
|
);
|
|
|
|
it.each(["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.2"] as const)(
|
|
"passes 'low' / 'medium' / 'high' / 'xhigh' through unchanged for %s",
|
|
(modelId) => {
|
|
expect(resolveReasoningEffort("low", modelId)).toBe("low");
|
|
expect(resolveReasoningEffort("medium", modelId)).toBe("medium");
|
|
expect(resolveReasoningEffort("high", modelId)).toBe("high");
|
|
expect(resolveReasoningEffort("xhigh", modelId)).toBe("xhigh");
|
|
},
|
|
);
|
|
|
|
it("normalizes case-variant model ids", () => {
|
|
expect(resolveReasoningEffort("minimal", "GPT-5.5")).toBe("low");
|
|
expect(resolveReasoningEffort("minimal", " gpt-5.4-mini ")).toBe("low");
|
|
});
|
|
});
|
|
|
|
describe("legacy / non-modern Codex models", () => {
|
|
it.each(["gpt-5", "gpt-4o", "o3-mini", "codex-mini-latest"] as const)(
|
|
"preserves 'minimal' for %s — pre-modern enum still supports it",
|
|
(modelId) => {
|
|
expect(resolveReasoningEffort("minimal", modelId)).toBe("minimal");
|
|
},
|
|
);
|
|
|
|
it("preserves 'minimal' for empty / unknown model ids (conservative default)", () => {
|
|
expect(resolveReasoningEffort("minimal", "")).toBe("minimal");
|
|
expect(resolveReasoningEffort("minimal", "unknown-model-xyz")).toBe("minimal");
|
|
});
|
|
});
|
|
|
|
describe("non-effort thinkLevel values", () => {
|
|
it("returns null for 'off'", () => {
|
|
expect(resolveReasoningEffort("off", "gpt-5.5")).toBeNull();
|
|
expect(resolveReasoningEffort("off", "gpt-4o")).toBeNull();
|
|
});
|
|
|
|
it("returns null for 'adaptive' (non-effort enum value)", () => {
|
|
expect(resolveReasoningEffort("adaptive", "gpt-5.5")).toBeNull();
|
|
expect(resolveReasoningEffort("adaptive", "gpt-4o")).toBeNull();
|
|
});
|
|
|
|
it("returns null for 'max' (non-effort enum value)", () => {
|
|
expect(resolveReasoningEffort("max", "gpt-5.5")).toBeNull();
|
|
expect(resolveReasoningEffort("max", "gpt-4o")).toBeNull();
|
|
});
|
|
});
|
|
});
|