fix(agents): respect explicit user compat overrides for non-native openai-completions (#44432)

Reviewed-by: @frankekn
This commit is contained in:
cheapestinference 2026-03-13 10:30:24 +01:00 committed by GitHub
parent 84428bbba6
commit 60cb1d683c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 8 deletions

View File

@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai
- Discord/gateway startup: treat plain-text and transient `/gateway/bot` metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman.
- Gateway/session reset: preserve `lastAccountId` and `lastThreadId` across gateway session resets so replies keep routing back to the same account and thread after `/reset`. (#44773) Thanks @Lanfei.
- Agents/memory bootstrap: load only one root memory file, preferring `MEMORY.md` and using `memory.md` as a fallback, so case-insensitive Docker mounts no longer inject duplicate memory context. (#26054) Thanks @Lanfei.
- Agents/OpenAI-compatible compat overrides: respect explicit user `models[].compat` opt-ins for non-native `openai-completions` endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference.
## 2026.3.12

View File

@ -251,7 +251,7 @@ describe("normalizeModelCompat", () => {
});
});
it("overrides explicit supportsDeveloperRole true on non-native endpoints", () => {
it("respects explicit supportsDeveloperRole true on non-native endpoints", () => {
const model = {
...baseModel(),
provider: "custom-cpa",
@ -259,10 +259,10 @@ describe("normalizeModelCompat", () => {
compat: { supportsDeveloperRole: true },
};
const normalized = normalizeModelCompat(model);
expect(supportsDeveloperRole(normalized)).toBe(false);
expect(supportsDeveloperRole(normalized)).toBe(true);
});
it("overrides explicit supportsUsageInStreaming true on non-native endpoints", () => {
it("respects explicit supportsUsageInStreaming true on non-native endpoints", () => {
const model = {
...baseModel(),
provider: "custom-cpa",
@ -270,6 +270,18 @@ describe("normalizeModelCompat", () => {
compat: { supportsUsageInStreaming: true },
};
const normalized = normalizeModelCompat(model);
expect(supportsUsageInStreaming(normalized)).toBe(true);
});
it("still forces flags off when not explicitly set by user", () => {
const model = {
...baseModel(),
provider: "custom-cpa",
baseUrl: "https://proxy.example.com/v1",
};
delete (model as { compat?: unknown }).compat;
const normalized = normalizeModelCompat(model);
expect(supportsDeveloperRole(normalized)).toBe(false);
expect(supportsUsageInStreaming(normalized)).toBe(false);
});

View File

@ -55,17 +55,22 @@ export function normalizeModelCompat(model: Model<Api>): Model<Api> {
// The `developer` role and stream usage chunks are OpenAI-native behaviors.
// Many OpenAI-compatible backends reject `developer` and/or emit usage-only
// chunks that break strict parsers expecting choices[0]. For non-native
// openai-completions endpoints, force both compat flags off.
// openai-completions endpoints, force both compat flags off — unless the
// user has explicitly opted in via their model config.
const compat = model.compat ?? undefined;
// When baseUrl is empty the pi-ai library defaults to api.openai.com, so
// leave compat unchanged and let default native behavior apply.
// Note: explicit true values are intentionally overridden for non-native
// endpoints for safety.
const needsForce = baseUrl ? !isOpenAINativeEndpoint(baseUrl) : false;
if (!needsForce) {
return model;
}
if (compat?.supportsDeveloperRole === false && compat?.supportsUsageInStreaming === false) {
// Respect explicit user overrides: if the user has set a compat flag to
// true in their model definition, they know their endpoint supports it.
const forcedDeveloperRole = compat?.supportsDeveloperRole === true;
const forcedUsageStreaming = compat?.supportsUsageInStreaming === true;
if (forcedDeveloperRole && forcedUsageStreaming) {
return model;
}
@ -73,7 +78,11 @@ export function normalizeModelCompat(model: Model<Api>): Model<Api> {
return {
...model,
compat: compat
? { ...compat, supportsDeveloperRole: false, supportsUsageInStreaming: false }
? {
...compat,
supportsDeveloperRole: forcedDeveloperRole || false,
supportsUsageInStreaming: forcedUsageStreaming || false,
}
: { supportsDeveloperRole: false, supportsUsageInStreaming: false },
} as typeof model;
}