mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(agents): allow Codex runtime for Codex provider (#103775)
This commit is contained in:
committed by
GitHub
parent
d628e35574
commit
db39fe8072
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Codex runtime switching:** accept the bundled Codex runtime for both `codex/*` and `openai/*` model routes while keeping unsupported provider/runtime pairs rejected. (#103762)
|
||||
- **Agent abort cleanup:** serialize prompt lock reacquisition with terminal cleanup so canceled embedded runs do not self-contend on session locks for up to 60 seconds.
|
||||
- **Chutes OAuth deadlines:** bound token exchange, profile lookup, and refresh requests, and keep issued tokens when optional userinfo enrichment stalls. (#102026) Thanks @Alix-007.
|
||||
- **Control UI workspace avatars:** inline validated agent avatar files in bootstrap and identity responses so Personal card images render without unauthenticated avatar-route requests, while preserving configured emoji precedence. (#102892, #97602) Thanks @LZY3538.
|
||||
|
||||
@@ -24,6 +24,27 @@ export function resolvePersistedSessionRuntimeId(
|
||||
return normalizeOptionalAgentRuntimeId(entry?.agentHarnessId);
|
||||
}
|
||||
|
||||
/** Resolves a runtime id only when it can serve the selected provider. */
|
||||
export function resolveCompatibleAgentRuntimeForProvider(params: {
|
||||
provider?: string | null;
|
||||
runtime?: string | null;
|
||||
cfg?: OpenClawConfig;
|
||||
}): string | undefined {
|
||||
const runtime = normalizeOptionalAgentRuntimeId(params.runtime);
|
||||
if (!runtime || isDefaultAgentRuntimeId(runtime)) {
|
||||
return undefined;
|
||||
}
|
||||
if (runtime === "openclaw") {
|
||||
return runtime;
|
||||
}
|
||||
const provider = params.provider?.trim().toLowerCase() ?? "";
|
||||
// The Codex harness owns both OpenClaw's virtual Codex namespace and canonical OpenAI routes.
|
||||
if (runtime === "codex" && (provider === "codex" || provider === "openai")) {
|
||||
return runtime;
|
||||
}
|
||||
return isCliRuntimeAliasForProvider({ provider, runtime, cfg: params.cfg }) ? runtime : undefined;
|
||||
}
|
||||
|
||||
/** Resolves a persisted runtime override only when it can serve the selected provider. */
|
||||
export function resolveSessionRuntimeOverrideForProvider(params: {
|
||||
provider?: string | null;
|
||||
@@ -32,16 +53,9 @@ export function resolveSessionRuntimeOverrideForProvider(params: {
|
||||
}): string | undefined {
|
||||
// agentHarnessId records the runtime that produced the existing transcript;
|
||||
// it must not override the runtime selected for the next turn.
|
||||
const runtime = normalizeOptionalAgentRuntimeId(params.entry?.agentRuntimeOverride);
|
||||
if (!runtime || isDefaultAgentRuntimeId(runtime)) {
|
||||
return undefined;
|
||||
}
|
||||
if (runtime === "openclaw") {
|
||||
return runtime;
|
||||
}
|
||||
const provider = params.provider?.trim().toLowerCase() ?? "";
|
||||
if (provider === "openai" && runtime === "codex") {
|
||||
return runtime;
|
||||
}
|
||||
return isCliRuntimeAliasForProvider({ provider, runtime, cfg: params.cfg }) ? runtime : undefined;
|
||||
return resolveCompatibleAgentRuntimeForProvider({
|
||||
provider: params.provider,
|
||||
runtime: params.entry?.agentRuntimeOverride,
|
||||
cfg: params.cfg,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,6 +86,19 @@ describe("resolveSessionRuntimeOverrideForProvider", () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ provider: "openai", expected: "codex" },
|
||||
{ provider: "codex", expected: "codex" },
|
||||
{ provider: "anthropic", expected: undefined },
|
||||
])("resolves Codex runtime compatibility for $provider", ({ provider, expected }) => {
|
||||
expect(
|
||||
resolveSessionRuntimeOverrideForProvider({
|
||||
provider,
|
||||
entry: { agentRuntimeOverride: "codex" },
|
||||
}),
|
||||
).toBe(expected);
|
||||
});
|
||||
|
||||
it("keeps CLI runtime pins only when the runtime serves the selected provider", () => {
|
||||
cliBackendsTesting.setDepsForTest({
|
||||
resolveRuntimeCliBackends: () => [],
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
isDefaultAgentRuntimeId,
|
||||
normalizeOptionalAgentRuntimeId,
|
||||
} from "../../agents/agent-runtime-id.js";
|
||||
import { resolveCliRuntimeModelBackendBinding } from "../../agents/cli-backends.js";
|
||||
import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||
import { resolveSessionRuntimeOverrideForProvider } from "../../agents/session-runtime-compat.js";
|
||||
import {
|
||||
resolveCompatibleAgentRuntimeForProvider,
|
||||
resolveSessionRuntimeOverrideForProvider,
|
||||
} from "../../agents/session-runtime-compat.js";
|
||||
import type { SessionEntry } from "../../config/sessions/types.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
|
||||
@@ -42,21 +44,15 @@ export function resolveModelRuntimeDirective(params: {
|
||||
if (isDefaultAgentRuntimeId(runtime)) {
|
||||
return { kind: "clear" };
|
||||
}
|
||||
if (runtime === "openclaw") {
|
||||
return { kind: "set", runtime };
|
||||
}
|
||||
|
||||
const provider = normalizeProviderId(params.provider);
|
||||
if (provider === "openai" && runtime === "codex") {
|
||||
return { kind: "set", runtime };
|
||||
}
|
||||
const backend = resolveCliRuntimeModelBackendBinding({
|
||||
config: params.cfg,
|
||||
const compatibleRuntime = resolveCompatibleAgentRuntimeForProvider({
|
||||
provider,
|
||||
runtime,
|
||||
cfg: params.cfg,
|
||||
});
|
||||
if (backend) {
|
||||
return { kind: "set", runtime: backend.runtime };
|
||||
if (compatibleRuntime) {
|
||||
return { kind: "set", runtime: compatibleRuntime };
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1306,14 +1306,17 @@ describe("/model chat UX", () => {
|
||||
expect(sessionEntry.authProfileOverride).toBe(OPENAI_DATE_PROFILE_ID);
|
||||
});
|
||||
|
||||
it("persists provider-compatible runtime overrides for mixed-content messages", async () => {
|
||||
it.each([
|
||||
["openai/gpt-4o", "openai", "gpt-4o"],
|
||||
["codex/gpt-5.5", "codex", "gpt-5.5"],
|
||||
])("persists provider-compatible runtime overrides for %s", async (modelKey, provider, model) => {
|
||||
const { persisted, sessionEntry } = await persistModelDirectiveForTest({
|
||||
command: "/model openai/gpt-4o --runtime codex hello",
|
||||
allowedModelKeys: ["openai/gpt-4o"],
|
||||
command: `/model ${modelKey} --runtime codex hello`,
|
||||
allowedModelKeys: [modelKey],
|
||||
});
|
||||
|
||||
expect(sessionEntry.providerOverride).toBe("openai");
|
||||
expect(sessionEntry.modelOverride).toBe("gpt-4o");
|
||||
expect(sessionEntry.providerOverride).toBe(provider);
|
||||
expect(sessionEntry.modelOverride).toBe(model);
|
||||
expect(sessionEntry.agentRuntimeOverride).toBe("codex");
|
||||
expect(persisted.runtimeChange).toEqual({ kind: "set", runtime: "codex" });
|
||||
});
|
||||
@@ -1443,6 +1446,16 @@ describe("/model chat UX", () => {
|
||||
expect(enqueueSystemEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects the Codex runtime for providers the harness does not support", async () => {
|
||||
const { persisted, sessionEntry } = await persistModelDirectiveForTest({
|
||||
command: "/model anthropic/claude-opus-4-6 --runtime codex hello",
|
||||
allowedModelKeys: ["anthropic/claude-opus-4-6"],
|
||||
});
|
||||
|
||||
expect(persisted.errorText).toBe('Runtime "codex" is not supported for anthropic.');
|
||||
expect(sessionEntry.agentRuntimeOverride).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects unsupported mixed thinking before mutating the model/runtime transaction", async () => {
|
||||
setOpenAiRuntimeScopedUltraProvider();
|
||||
const sessionEntry = createSessionEntry({
|
||||
|
||||
Reference in New Issue
Block a user