mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 21:04:45 +00:00
fix: honor wildcard model runtime policy
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/OpenAI-compatible: honor per-model `max_completion_tokens`/`max_tokens` params in embedded OpenAI-completions runs so high-token Kimi-style routes keep their configured completion cap. Fixes #82230. Thanks @albert-zen.
|
||||
- Agents/local: install a local gateway request scope around trusted `openclaw agent --local` runs, so subagent completion announces can use in-process gateway dispatch without crashing. Fixes #82140. Thanks @Kushmaro.
|
||||
- Discord: validate message-read results before normalizing channel history and report unexpected payloads with a Discord boundary error instead of `map is not a function`. Fixes #82252. Thanks @jessewunderlich.
|
||||
- Agents/runtime: apply `agents.defaults.models["provider/*"].agentRuntime` as provider-wide model runtime policy while preserving exact model runtime precedence. Fixes #82243. Thanks @rendrag-git.
|
||||
- Telegram/active-memory: run blocking memory recall through the Telegram provider for direct-message turns even when the hook context carries the raw chat id, preventing embedded recall from launching against an invalid numeric channel. Fixes #82177. Thanks @cslash-zz.
|
||||
- Control UI/WebChat: keep optimistic image messages from embedding large inline `data:` previews and preserve image-only user turns in chat history, avoiding browser stack overflows when sending image attachments. Fixes #82182. Thanks @ExploreSheep.
|
||||
- Agents/media: preserve message-tool-only delivery for generated music and video completion handoffs, so group/channel completions do not finish without posting the generated attachment.
|
||||
|
||||
@@ -220,6 +220,18 @@ describe("runAgentHarnessAttempt", () => {
|
||||
expect(piRunAttempt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("honors provider wildcard PI runtime policy for OpenAI agent model runs", async () => {
|
||||
registerSuccessfulCodexHarness();
|
||||
|
||||
const result = await runAgentHarnessAttempt({
|
||||
...createAttemptParams(agentModelRuntimeConfig("openai/*", "pi")),
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.4",
|
||||
});
|
||||
expect(result.sessionIdUsed).toBe("pi");
|
||||
expect(piRunAttempt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("annotates non-ok harness result classifications for outer model fallback", async () => {
|
||||
const classify = vi.fn<NonNullable<AgentHarness["classify"]>>(() => "empty" as const);
|
||||
registerAgentHarness(
|
||||
|
||||
113
src/agents/model-runtime-policy.test.ts
Normal file
113
src/agents/model-runtime-policy.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveModelRuntimePolicy } from "./model-runtime-policy.js";
|
||||
|
||||
describe("resolveModelRuntimePolicy", () => {
|
||||
it("honors provider wildcard agent model runtime policy entries", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"vllm/*": { agentRuntime: { id: "pi" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveModelRuntimePolicy({
|
||||
config,
|
||||
provider: "vllm",
|
||||
modelId: "qwen-local",
|
||||
}),
|
||||
).toEqual({
|
||||
policy: { id: "pi" },
|
||||
source: "model",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers exact agent model runtime policy entries over provider wildcards", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"vllm/*": { agentRuntime: { id: "pi" } },
|
||||
"vllm/qwen-local": { agentRuntime: { id: "codex" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveModelRuntimePolicy({
|
||||
config,
|
||||
provider: "vllm",
|
||||
modelId: "qwen-local",
|
||||
}),
|
||||
).toEqual({
|
||||
policy: { id: "codex" },
|
||||
source: "model",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers exact provider model runtime policy over agent provider wildcards", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"vllm/*": { agentRuntime: { id: "pi" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
vllm: {
|
||||
models: [{ id: "qwen-local", agentRuntime: { id: "codex" } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveModelRuntimePolicy({
|
||||
config,
|
||||
provider: "vllm",
|
||||
modelId: "qwen-local",
|
||||
}),
|
||||
).toEqual({
|
||||
policy: { id: "codex" },
|
||||
source: "model",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers agent provider wildcard runtime policy over provider runtime policy", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"vllm/*": { agentRuntime: { id: "pi" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
vllm: {
|
||||
agentRuntime: { id: "codex" },
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveModelRuntimePolicy({
|
||||
config,
|
||||
provider: "vllm",
|
||||
modelId: "qwen-local",
|
||||
}),
|
||||
).toEqual({
|
||||
policy: { id: "pi" },
|
||||
source: "model",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,8 @@ export type ResolvedModelRuntimePolicy = {
|
||||
source?: ModelRuntimePolicySource;
|
||||
};
|
||||
|
||||
type ModelEntryMatchKind = "none" | "exact" | "provider-wildcard";
|
||||
|
||||
function hasRuntimePolicy(value: AgentRuntimePolicyConfig | undefined): boolean {
|
||||
return Boolean(value?.id?.trim());
|
||||
}
|
||||
@@ -63,26 +65,41 @@ function modelEntryMatches(params: {
|
||||
provider: string | undefined;
|
||||
modelId: string;
|
||||
}): boolean {
|
||||
return modelEntryMatchKind(params) === "exact";
|
||||
}
|
||||
|
||||
function modelEntryMatchKind(params: {
|
||||
entry: Pick<ModelDefinitionConfig, "id">;
|
||||
provider: string | undefined;
|
||||
modelId: string;
|
||||
}): ModelEntryMatchKind {
|
||||
const entryId = params.entry.id.trim();
|
||||
if (entryId === params.modelId) {
|
||||
return true;
|
||||
return "exact";
|
||||
}
|
||||
const slash = entryId.indexOf("/");
|
||||
if (slash <= 0) {
|
||||
return false;
|
||||
return "none";
|
||||
}
|
||||
return (
|
||||
normalizeProviderId(entryId.slice(0, slash)) === normalizeProviderId(params.provider ?? "") &&
|
||||
entryId.slice(slash + 1).trim() === params.modelId
|
||||
);
|
||||
if (normalizeProviderId(entryId.slice(0, slash)) !== normalizeProviderId(params.provider ?? "")) {
|
||||
return "none";
|
||||
}
|
||||
const entryModelId = entryId.slice(slash + 1).trim();
|
||||
if (entryModelId === params.modelId) {
|
||||
return "exact";
|
||||
}
|
||||
if (entryModelId === "*") {
|
||||
return "provider-wildcard";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
function modelKeyMatches(params: {
|
||||
function modelKeyMatchKind(params: {
|
||||
key: string;
|
||||
provider: string | undefined;
|
||||
modelId: string;
|
||||
}): boolean {
|
||||
return modelEntryMatches({
|
||||
}): ModelEntryMatchKind {
|
||||
return modelEntryMatchKind({
|
||||
entry: { id: params.key },
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
@@ -95,6 +112,7 @@ function resolveAgentModelEntryRuntimePolicy(params: {
|
||||
modelId?: string;
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
matchKind: Exclude<ModelEntryMatchKind, "none">;
|
||||
}): ResolvedModelRuntimePolicy {
|
||||
const modelId = normalizeModelIdForProvider(params.provider, params.modelId);
|
||||
if (!params.config || !modelId) {
|
||||
@@ -115,7 +133,7 @@ function resolveAgentModelEntryRuntimePolicy(params: {
|
||||
for (const models of modelMaps) {
|
||||
for (const [key, entry] of Object.entries(models ?? {})) {
|
||||
if (
|
||||
modelKeyMatches({ key, provider: params.provider, modelId }) &&
|
||||
modelKeyMatchKind({ key, provider: params.provider, modelId }) === params.matchKind &&
|
||||
hasRuntimePolicy(entry?.agentRuntime)
|
||||
) {
|
||||
return { policy: entry.agentRuntime, source: "model" };
|
||||
@@ -146,7 +164,7 @@ export function resolveModelRuntimePolicy(params: {
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
}): ResolvedModelRuntimePolicy {
|
||||
const agentModelPolicy = resolveAgentModelEntryRuntimePolicy(params);
|
||||
const agentModelPolicy = resolveAgentModelEntryRuntimePolicy({ ...params, matchKind: "exact" });
|
||||
if (agentModelPolicy.policy) {
|
||||
return agentModelPolicy;
|
||||
}
|
||||
@@ -159,6 +177,13 @@ export function resolveModelRuntimePolicy(params: {
|
||||
if (hasRuntimePolicy(modelConfig?.agentRuntime)) {
|
||||
return { policy: modelConfig?.agentRuntime, source: "model" };
|
||||
}
|
||||
const agentWildcardModelPolicy = resolveAgentModelEntryRuntimePolicy({
|
||||
...params,
|
||||
matchKind: "provider-wildcard",
|
||||
});
|
||||
if (agentWildcardModelPolicy.policy) {
|
||||
return agentWildcardModelPolicy;
|
||||
}
|
||||
if (hasRuntimePolicy(providerConfig?.agentRuntime)) {
|
||||
return { policy: providerConfig?.agentRuntime, source: "provider" };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user