From 38eea33062e4b83e9a961056babb89f4e9901484 Mon Sep 17 00:00:00 2001 From: MkDev11 <94194147+MkDev11@users.noreply.github.com> Date: Sun, 3 May 2026 08:22:07 -0700 Subject: [PATCH] fix(opencode): expose Claude thinking policy (#76760) Summary: - Adds an OpenCode provider-policy public artifact that delegates Claude thinking profiles, plus regression tests and a changelog entry for preserving `xhigh` on `opencode/claude-opus-4-7`. - Reproducibility: yes. Source inspection on current main shows the model-switch path remaps unsupported store ... vider-policy-api` artifact for the fallback resolver; the proposed test exercises that stored-`xhigh` path. Automerge notes: - No ClawSweeper repair was needed after automerge opt-in. Validation: - ClawSweeper review passed for head efa152cca544e0e5c998601a40c0229cc5504524. - Required merge gates passed before the squash merge. Prepared head SHA: efa152cca544e0e5c998601a40c0229cc5504524 Review: https://github.com/openclaw/openclaw/pull/76760#issuecomment-4366483080 Co-authored-by: mkdev11 --- CHANGELOG.md | 1 + .../opencode/provider-policy-api.test.ts | 29 +++++++++++++++++++ extensions/opencode/provider-policy-api.ts | 5 ++++ .../reply/directive-handling.model.test.ts | 22 ++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 extensions/opencode/provider-policy-api.test.ts create mode 100644 extensions/opencode/provider-policy-api.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d5f7f65f64c..6dabc95a267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai - Plugins/context-engine: include the selected `plugins.slots.contextEngine` plugin in the gateway startup load plan so external context-engine plugins without `activation.onStartup` in their manifest are loaded before any agent turn resolves the active engine; prevents the "Context engine X is not registered; falling back to default engine legacy" warning after gateway startup. Fixes #76576. Thanks @hclsys. - Plugins/tools: restore on-demand registry load for path-based plugins (origin "config") so tool factories registered via `plugins.load.paths` are resolved at agent request time when no pre-warmed channel registry is present; prevents "unknown method" errors after gateway startup. Fixes #76598. Thanks @hclsys. - Plugins/hooks: include explicitly enabled hook-capable plugins in the Gateway startup runtime scope so embedded PI runs can see their `before_prompt_build` and `agent_end` hooks. Fixes #76649. Thanks @wwf3045 and @MkDev11. +- Plugins/OpenCode: expose Claude thinking profiles through the lightweight provider policy surface so directive and session validation keep `xhigh`, `adaptive`, and `max` for `opencode/claude-opus-4-7` instead of remapping `xhigh` to `high`. Fixes #76648. Thanks @aaajiao. - Channels/QQ Bot: resolve structured `clientSecret` SecretRefs before QQ token exchange, expose the QQ Bot secret contract to secrets tooling, and reject legacy `secretref:/...` marker strings. (#74772) Thanks @xialonglee. - Agents: keep active streamed provider replies alive by refreshing guarded fetch timeouts on raw body chunks and surface true prompt stream timeouts as explicit errors instead of partial assistant fragments. Fixes #76307. (#76633) Thanks @MkDev11. - Plugins/externalization: keep official ACPX, Google Chat, and LINE install specs on production package names, leaving beta-tag probing to the explicit OpenClaw beta update channel. Thanks @vincentkoc. diff --git a/extensions/opencode/provider-policy-api.test.ts b/extensions/opencode/provider-policy-api.test.ts new file mode 100644 index 00000000000..a89e0a9b4e7 --- /dev/null +++ b/extensions/opencode/provider-policy-api.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { resolveThinkingProfile } from "./provider-policy-api.js"; + +describe("opencode provider policy public artifact", () => { + it("exposes Claude Opus 4.7 thinking levels without loading the full provider plugin", () => { + expect( + resolveThinkingProfile({ + provider: "opencode", + modelId: "claude-opus-4-7", + }), + ).toMatchObject({ + levels: expect.arrayContaining([{ id: "xhigh" }, { id: "adaptive" }, { id: "max" }]), + defaultLevel: "off", + }); + }); + + it("keeps adaptive-only Claude profiles aligned with Anthropic", () => { + const profile = resolveThinkingProfile({ + provider: "opencode", + modelId: "claude-opus-4-6", + }); + + expect(profile).toMatchObject({ + levels: expect.arrayContaining([{ id: "adaptive" }]), + defaultLevel: "adaptive", + }); + expect(profile.levels.some((level) => level.id === "xhigh" || level.id === "max")).toBe(false); + }); +}); diff --git a/extensions/opencode/provider-policy-api.ts b/extensions/opencode/provider-policy-api.ts new file mode 100644 index 00000000000..cc1aedcdf23 --- /dev/null +++ b/extensions/opencode/provider-policy-api.ts @@ -0,0 +1,5 @@ +import { resolveClaudeThinkingProfile } from "openclaw/plugin-sdk/provider-model-shared"; + +export function resolveThinkingProfile(params: { provider?: string; modelId: string }) { + return resolveClaudeThinkingProfile(params.modelId); +} diff --git a/src/auto-reply/reply/directive-handling.model.test.ts b/src/auto-reply/reply/directive-handling.model.test.ts index c6f3b1d5b24..ff41ea6e17f 100644 --- a/src/auto-reply/reply/directive-handling.model.test.ts +++ b/src/auto-reply/reply/directive-handling.model.test.ts @@ -985,6 +985,28 @@ describe("handleDirectiveOnly model persist behavior (fixes #1435)", () => { }); }); + it("keeps xhigh when switching to OpenCode Claude Opus 4.7", async () => { + const sessionEntry = createSessionEntry({ thinkingLevel: "xhigh" }); + const sessionStore = { [sessionKey]: sessionEntry }; + + const result = await handleDirectiveOnly( + createHandleParams({ + directives: parseInlineDirectives("/model opencode/claude-opus-4-7"), + allowedModelKeys: new Set([...allowedModelKeys, "opencode/claude-opus-4-7"]), + allowedModelCatalog: [ + ...allowedModelCatalog, + { provider: "opencode", id: "claude-opus-4-7", name: "Claude Opus 4.7" }, + ], + sessionEntry, + sessionStore, + }), + ); + + expect(result?.text).toContain("Model set to opencode/claude-opus-4-7 for this session."); + expect(result?.text ?? "").not.toContain("xhigh not supported"); + expect(sessionEntry.thinkingLevel).toBe("xhigh"); + }); + it("does not request a live restart when /model mutates an active session", async () => { const directives = parseInlineDirectives("/model openai/gpt-4o"); const sessionEntry = createSessionEntry();