mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 08:34:46 +00:00
fix: route plugin LLM completions through Codex runtime (#81511)
* fix: route plugin LLM completions through Codex runtime * fix: preserve OpenRouter completion model ids * fix: allow registry config compat guards
This commit is contained in:
@@ -67,6 +67,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway: keep active reply runs visible to stuck-session diagnostics and clear no-active-work recovery state, preventing stale queued lanes after compaction or tool failures. Fixes #80677. (#81302)
|
||||
- Codex app-server: rotate incompatible context-engine-managed native threads so Lossless-managed sessions do not resume stale hidden Codex history. (#81223) Thanks @jalehman.
|
||||
- Codex cron: execute scheduled command-style automation payloads before workspace bootstrap or memory review, preserving existing isolated cron jobs after Codex harness migration. (#81510) Thanks @jalehman.
|
||||
- Plugin LLM completions: honor Codex agent-runtime policy for canonical OpenAI model refs, so context-engine summarizers can use Codex OAuth instead of requiring direct `OPENAI_API_KEY` auth. (#81511) Thanks @jalehman.
|
||||
- Gateway/OpenAI HTTP: return OpenAI-compatible 400 errors for invalid sampling params and provider validation failures instead of collapsing them to 500s. (#81275) Thanks @Lellansin.
|
||||
- Telegram: publish plugin and skill command description localizations to native command menus while filtering unsupported locale codes and preserving Telegram command limits. (#81351) Thanks @jzakirov.
|
||||
- Limit hook CLI tool authority [AI]. (#81065) Thanks @pgondhi987.
|
||||
|
||||
@@ -13,6 +13,8 @@ const COMPAT_CONFIG_API_FILES = new Set([
|
||||
"src/plugin-sdk/config-runtime.ts",
|
||||
"src/plugin-sdk/memory-core-host-runtime-core.ts",
|
||||
"src/plugins/compat/registry.ts",
|
||||
"src/plugins/registry.runtime-config.test.ts",
|
||||
"src/plugins/registry.ts",
|
||||
"src/plugins/contracts/config-boundary-guard.test.ts",
|
||||
"src/plugins/contracts/deprecated-internal-config-api.test.ts",
|
||||
"src/plugins/registry.runtime-config.test.ts",
|
||||
|
||||
@@ -74,6 +74,26 @@ describe("resolveSimpleCompletionSelectionForAgent", () => {
|
||||
expect(selection.profileId).toBe("work");
|
||||
});
|
||||
|
||||
it("uses Codex execution provider for OpenAI model refs with Codex runtime policy", () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4-mini",
|
||||
models: {
|
||||
"openai/gpt-5.4-mini": { agentRuntime: { id: "codex" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const selection = requireSelection(
|
||||
resolveSimpleCompletionSelectionForAgent({ cfg, agentId: "main" }),
|
||||
);
|
||||
expect(selection.provider).toBe("openai");
|
||||
expect(selection.modelId).toBe("gpt-5.4-mini");
|
||||
expect(selection.runtimeProvider).toBe("openai-codex");
|
||||
});
|
||||
|
||||
it("falls back to runtime default model when no explicit model is configured", () => {
|
||||
const cfg = {} as OpenClawConfig;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Model } from "@earendil-works/pi-ai";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
|
||||
const hoisted = vi.hoisted(() => ({
|
||||
resolveModelMock: vi.fn(),
|
||||
@@ -41,10 +42,14 @@ vi.mock("../plugins/provider-runtime.runtime.js", () => ({
|
||||
|
||||
let completeWithPreparedSimpleCompletionModel: typeof import("./simple-completion-runtime.js").completeWithPreparedSimpleCompletionModel;
|
||||
let prepareSimpleCompletionModel: typeof import("./simple-completion-runtime.js").prepareSimpleCompletionModel;
|
||||
let prepareSimpleCompletionModelForAgent: typeof import("./simple-completion-runtime.js").prepareSimpleCompletionModelForAgent;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ completeWithPreparedSimpleCompletionModel, prepareSimpleCompletionModel } =
|
||||
await import("./simple-completion-runtime.js"));
|
||||
({
|
||||
completeWithPreparedSimpleCompletionModel,
|
||||
prepareSimpleCompletionModel,
|
||||
prepareSimpleCompletionModelForAgent,
|
||||
} = await import("./simple-completion-runtime.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -500,6 +505,50 @@ describe("prepareSimpleCompletionModel", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("prepareSimpleCompletionModelForAgent", () => {
|
||||
it("uses Codex auth provider for OpenAI model refs with Codex runtime policy", async () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4-mini",
|
||||
models: {
|
||||
"openai/gpt-5.4-mini": { agentRuntime: { id: "codex" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
hoisted.resolveModelMock.mockReturnValueOnce({
|
||||
model: {
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.4-mini",
|
||||
},
|
||||
authStorage: {
|
||||
setRuntimeApiKey: hoisted.setRuntimeApiKeyMock,
|
||||
},
|
||||
modelRegistry: {},
|
||||
});
|
||||
|
||||
const result = await prepareSimpleCompletionModelForAgent({
|
||||
cfg,
|
||||
agentId: "main",
|
||||
});
|
||||
|
||||
expectPreparedModelResult(result);
|
||||
expect(result.selection.provider).toBe("openai");
|
||||
expect(result.selection.modelId).toBe("gpt-5.4-mini");
|
||||
expect(result.selection.runtimeProvider).toBe("openai-codex");
|
||||
expect(hoisted.resolveModelMock).toHaveBeenCalledWith(
|
||||
"openai-codex",
|
||||
"gpt-5.4-mini",
|
||||
expect.any(String),
|
||||
cfg,
|
||||
);
|
||||
expect(
|
||||
(callArg(hoisted.getApiKeyForModelMock) as { model?: { provider?: string } }).model?.provider,
|
||||
).toBe("openai-codex");
|
||||
});
|
||||
});
|
||||
|
||||
describe("completeWithPreparedSimpleCompletionModel", () => {
|
||||
it("prepares provider-owned stream APIs before running a completion", async () => {
|
||||
const model = {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { prepareProviderRuntimeAuth } from "../plugins/provider-runtime.runtime.js";
|
||||
import { resolveAgentDir, resolveAgentEffectiveModelPrimary } from "./agent-scope.js";
|
||||
import { DEFAULT_PROVIDER } from "./defaults.js";
|
||||
import { resolveAgentHarnessPolicy } from "./harness/policy.js";
|
||||
import {
|
||||
applyLocalNoAuthHeaderOverride,
|
||||
getApiKeyForModel,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
resolveDefaultModelForAgent,
|
||||
resolveModelRefFromString,
|
||||
} from "./model-selection.js";
|
||||
import { OPENAI_CODEX_PROVIDER_ID, isOpenAIProvider } from "./openai-codex-routing.js";
|
||||
import { resolveModel, resolveModelAsync } from "./pi-embedded-runner/model.js";
|
||||
import { prepareModelForSimpleCompletion } from "./simple-completion-transport.js";
|
||||
|
||||
@@ -55,6 +57,8 @@ export type PreparedSimpleCompletionModel =
|
||||
export type AgentSimpleCompletionSelection = {
|
||||
provider: string;
|
||||
modelId: string;
|
||||
/** Provider used for auth/transport when runtime policy redirects the logical model ref. */
|
||||
runtimeProvider?: string;
|
||||
profileId?: string;
|
||||
agentDir: string;
|
||||
};
|
||||
@@ -102,11 +106,35 @@ export function resolveSimpleCompletionSelectionForAgent(params: {
|
||||
return {
|
||||
provider,
|
||||
modelId,
|
||||
...resolveSimpleCompletionRuntimeProvider({
|
||||
cfg: params.cfg,
|
||||
agentId: params.agentId,
|
||||
provider,
|
||||
modelId,
|
||||
}),
|
||||
profileId: split?.profile || undefined,
|
||||
agentDir: resolveAgentDir(params.cfg, params.agentId),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSimpleCompletionRuntimeProvider(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
provider: string;
|
||||
modelId: string;
|
||||
}): Pick<AgentSimpleCompletionSelection, "runtimeProvider"> {
|
||||
if (!isOpenAIProvider(params.provider)) {
|
||||
return {};
|
||||
}
|
||||
const policy = resolveAgentHarnessPolicy({
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
config: params.cfg,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
return policy.runtime === "codex" ? { runtimeProvider: OPENAI_CODEX_PROVIDER_ID } : {};
|
||||
}
|
||||
|
||||
async function setRuntimeApiKeyForCompletion(params: {
|
||||
authStorage: SimpleCompletionAuthStorage;
|
||||
model: Model<Api>;
|
||||
@@ -266,7 +294,7 @@ export async function prepareSimpleCompletionModelForAgent(params: {
|
||||
}
|
||||
const prepared = await prepareSimpleCompletionModel({
|
||||
cfg: params.cfg,
|
||||
provider: selection.provider,
|
||||
provider: selection.runtimeProvider ?? selection.provider,
|
||||
modelId: selection.modelId,
|
||||
agentDir: selection.agentDir,
|
||||
profileId: selection.profileId,
|
||||
|
||||
Reference in New Issue
Block a user