diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fcc92a6cb4..8a66d598c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai - Agents/Compaction: count auto-compactions only after a non-retry `auto_compaction_end`, keeping session `compactionCount` aligned to completed compactions. - Security/CLI: redact sensitive values in `openclaw config get` output before printing config paths, preventing credential leakage to terminal output/history. (#13683) Thanks @SleuthCo. +- Agents/Moonshot: force `supportsDeveloperRole=false` for Moonshot-compatible `openai-completions` models (provider `moonshot` and Moonshot base URLs), so initial runs no longer send unsupported `developer` roles that trigger `ROLE_UNSPECIFIED` errors. (#21060, #22194) Thanks @ShengFuC. - Install/Discord Voice: make `@discordjs/opus` an optional dependency so `openclaw` install/update no longer hard-fails when native Opus builds fail, while keeping `opusscript` as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman. - Docker/Setup: precreate `$OPENCLAW_CONFIG_DIR/identity` during `docker-setup.sh` so CLI commands that need device identity (for example `devices list`) avoid `EACCES ... /home/node/.openclaw/identity` failures on restrictive bind mounts. (#23948) Thanks @ackson-beep. - Exec/Background: stop applying the default exec timeout to background sessions (`background: true` or explicit `yieldMs`) when no explicit timeout is set, so long-running background jobs are no longer terminated at the default timeout boundary. (#23303) diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index 071f9cc9276..962724c665f 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -51,6 +51,32 @@ describe("normalizeModelCompat", () => { ).toBe(false); }); + it("forces supportsDeveloperRole off for moonshot models", () => { + const model = { + ...baseModel(), + provider: "moonshot", + baseUrl: "https://api.moonshot.ai/v1", + }; + delete (model as { compat?: unknown }).compat; + const normalized = normalizeModelCompat(model); + expect( + (normalized.compat as { supportsDeveloperRole?: boolean } | undefined)?.supportsDeveloperRole, + ).toBe(false); + }); + + it("forces supportsDeveloperRole off for custom moonshot-compatible endpoints", () => { + const model = { + ...baseModel(), + provider: "custom-kimi", + baseUrl: "https://api.moonshot.cn/v1", + }; + delete (model as { compat?: unknown }).compat; + const normalized = normalizeModelCompat(model); + expect( + (normalized.compat as { supportsDeveloperRole?: boolean } | undefined)?.supportsDeveloperRole, + ).toBe(false); + }); + it("leaves non-zai models untouched", () => { const model = { ...baseModel(), diff --git a/src/agents/model-compat.ts b/src/agents/model-compat.ts index e7b428e8442..d97d3965103 100644 --- a/src/agents/model-compat.ts +++ b/src/agents/model-compat.ts @@ -7,7 +7,11 @@ function isOpenAiCompletionsModel(model: Model): model is Model<"openai-com export function normalizeModelCompat(model: Model): Model { const baseUrl = model.baseUrl ?? ""; const isZai = model.provider === "zai" || baseUrl.includes("api.z.ai"); - if (!isZai || !isOpenAiCompletionsModel(model)) { + const isMoonshot = + model.provider === "moonshot" || + baseUrl.includes("moonshot.ai") || + baseUrl.includes("moonshot.cn"); + if ((!isZai && !isMoonshot) || !isOpenAiCompletionsModel(model)) { return model; }