Skip to content

fix(qwen): allow explicit qwen3.6-plus on Coding Plan#72664

Merged
vincentkoc merged 1 commit into
mainfrom
clownfish/ghcrawl-156617-autonomous-smoke
Apr 28, 2026
Merged

fix(qwen): allow explicit qwen3.6-plus on Coding Plan#72664
vincentkoc merged 1 commit into
mainfrom
clownfish/ghcrawl-156617-autonomous-smoke

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

Credit

This carries forward the focused fix from @jepson-liu in source PR #63987.

Validation

  • pnpm check:changed
  • pnpm -s vitest run extensions/qwen/index.test.ts src/agents/pi-embedded-runner/model.test.ts

Notes

#66367 is routed to central security handling by this cluster and is not used as an autonomous mutation target.

ProjectClownfish replacement details:

[email protected] check /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
pnpm check:no-conflict-markers && pnpm tool-display:check && pnpm check:host-env-policy:swift && pnpm tsgo && node scripts/prepare-extension-package-boundary-artifacts.mjs && pnpm lint && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope

[email protected] check:no-conflict-markers /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
node scripts/check-no-conflict-markers.mjs

[email protected] tool-display:check /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
node --import tsx scripts/tool-display.ts --check

tool-display snapshot is up to date

[email protected] check:host-env-policy:swift /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
node scripts/generate-host-env-security-policy-swift.mjs --check

OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift

[email protected] tsgo /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
node scripts/run-tsgo.mjs

[email protected] lint /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
node scripts/run-oxlint.mjs

Found 0 warnings and 0 errors.

[email protected] lint:webhook:no-low-level-body-read /tmp/projectclownfish-fix-4065D4/openclaw-openclaw
node scripts/check-webhook-auth-body-order.mjs

Found forbidden low-level body reads in auth-sensitive webhook handlers:

  • extensions/feishu/src/monitor.transport.ts:193
    Use plugin-sdk webhook guards (readJsonWebhookBodyOrReject / readWebhookBodyOrReject) with explicit pre-auth/post-auth profiles.
     ELIFECYCLE  Command failed with exit code 1.
     ELIFECYCLE  Command failed with exit code 1.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M maintainer Maintainer-authored PR labels Apr 27, 2026
@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes explicit qwen/qwen3.6-plus model resolution on Qwen Coding Plan endpoints by moving the shouldSuppressBuiltInModel check in resolveExplicitModelWithRegistry to run after findInlineModelMatch. It also threads config into buildSuppressedBuiltInModelError so the Qwen plugin can derive the baseUrl from the user's provider config and produce the correct suppression error message when the model is not inline-configured.

Confidence Score: 4/5

Safe to merge; the core logic change is correct and well-tested, with only minor test maintainability concerns.

No P0 or P1 issues found. Both P2 comments are about test maintainability (mock drift, undocumented SDK assumption) and do not affect production behavior. The suppression-reordering fix is correct, the new config parameter in buildUnknownModelError is a net improvement, and the inlined isQwenCodingPlanBaseUrl in the mock matches the canonical implementation in models.ts.

src/agents/pi-embedded-runner/model.test.ts — the inline mock logic should ideally be kept in sync with extensions/qwen/models.ts and extensions/qwen/index.ts.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/model.test.ts
Line: 5-38

Comment:
**Test mock duplicates production suppression logic**

The `vi.mock("../model-suppression.js")` factory inlines its own copies of `isQwenCodingPlanBaseUrl` and `resolveConfiguredQwenBaseUrl`. Both functions are already defined in `extensions/qwen/models.ts` and `extensions/qwen/index.ts`, respectively. Because the mock is not imported from the real module, any future change to the production logic (e.g., adding a new Coding Plan hostname, changing the `modelstudio` alias check) will silently leave the mock out of sync and the tests will continue to pass while the production path regresses. Consider importing the real helpers or, at minimum, adding a comment pointing to the canonical implementations.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/qwen/index.test.ts
Line: 31-44

Comment:
**`registerProvider` may never be called on the SDK-wrapped plugin**

`registerQwenProviderForTest` feeds a mock API with a `registerProvider` spy into `qwenPlugin.register`. The user-level `register(api)` in `index.ts` only calls `api.registerMediaUnderstandingProvider` and `api.registerVideoGenerationProvider` — it never calls `api.registerProvider`. This works only if `defineSingleProviderPluginEntry` internally wraps `register` so that its output calls `registerProvider` before the user callback runs. If that SDK contract were to change (or the test helper were ever reused against a plain plugin), the function would silently throw `"Qwen provider was not registered"`. A comment explaining the SDK wrapping assumption, or a test assertion on `provider !== undefined` before the throw, would make the invariant explicit.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(qwen): allow explicit qwen3.6-plus o..." | Re-trigger Greptile

Comment on lines +5 to +38
vi.mock("../model-suppression.js", () => {
function isQwenCodingPlanBaseUrl(value: string | undefined): boolean {
if (!value?.trim()) {
return false;
}
try {
const hostname = new URL(value).hostname.toLowerCase();
return (
hostname === "coding.dashscope.aliyuncs.com" ||
hostname === "coding-intl.dashscope.aliyuncs.com"
);
} catch {
return false;
}
}

function resolveConfiguredQwenBaseUrl(config: unknown): string | undefined {
const providers = (config as { models?: { providers?: Record<string, { baseUrl?: string }> } })
?.models?.providers;
if (!providers) {
return undefined;
}
return `Unknown model: ${provider}/gpt-5.3-codex-spark. gpt-5.3-codex-spark is no longer exposed by the OpenAI or Codex catalogs. Use openai/gpt-5.5.`;
},
}));
for (const [provider, entry] of Object.entries(providers)) {
const normalizedProvider = provider.trim().toLowerCase();
if (normalizedProvider !== "qwen" && normalizedProvider !== "modelstudio") {
continue;
}
const baseUrl = entry?.baseUrl?.trim();
if (baseUrl) {
return baseUrl;
}
}
return undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test mock duplicates production suppression logic

The vi.mock("../model-suppression.js") factory inlines its own copies of isQwenCodingPlanBaseUrl and resolveConfiguredQwenBaseUrl. Both functions are already defined in extensions/qwen/models.ts and extensions/qwen/index.ts, respectively. Because the mock is not imported from the real module, any future change to the production logic (e.g., adding a new Coding Plan hostname, changing the modelstudio alias check) will silently leave the mock out of sync and the tests will continue to pass while the production path regresses. Consider importing the real helpers or, at minimum, adding a comment pointing to the canonical implementations.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/model.test.ts
Line: 5-38

Comment:
**Test mock duplicates production suppression logic**

The `vi.mock("../model-suppression.js")` factory inlines its own copies of `isQwenCodingPlanBaseUrl` and `resolveConfiguredQwenBaseUrl`. Both functions are already defined in `extensions/qwen/models.ts` and `extensions/qwen/index.ts`, respectively. Because the mock is not imported from the real module, any future change to the production logic (e.g., adding a new Coding Plan hostname, changing the `modelstudio` alias check) will silently leave the mock out of sync and the tests will continue to pass while the production path regresses. Consider importing the real helpers or, at minimum, adding a comment pointing to the canonical implementations.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread extensions/qwen/index.test.ts Outdated
Comment on lines +31 to +44
};

function registerQwenProviderForTest(): RegisteredQwenProvider {
let provider: RegisteredQwenProvider | undefined;
qwenPlugin.register?.({
registerProvider: (next: RegisteredQwenProvider) => {
provider = next;
},
registerMediaUnderstandingProvider: () => {},
registerVideoGenerationProvider: () => {},
} as never);
if (!provider) {
throw new Error("Qwen provider was not registered");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 registerProvider may never be called on the SDK-wrapped plugin

registerQwenProviderForTest feeds a mock API with a registerProvider spy into qwenPlugin.register. The user-level register(api) in index.ts only calls api.registerMediaUnderstandingProvider and api.registerVideoGenerationProvider — it never calls api.registerProvider. This works only if defineSingleProviderPluginEntry internally wraps register so that its output calls registerProvider before the user callback runs. If that SDK contract were to change (or the test helper were ever reused against a plain plugin), the function would silently throw "Qwen provider was not registered". A comment explaining the SDK wrapping assumption, or a test assertion on provider !== undefined before the throw, would make the invariant explicit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/qwen/index.test.ts
Line: 31-44

Comment:
**`registerProvider` may never be called on the SDK-wrapped plugin**

`registerQwenProviderForTest` feeds a mock API with a `registerProvider` spy into `qwenPlugin.register`. The user-level `register(api)` in `index.ts` only calls `api.registerMediaUnderstandingProvider` and `api.registerVideoGenerationProvider` — it never calls `api.registerProvider`. This works only if `defineSingleProviderPluginEntry` internally wraps `register` so that its output calls `registerProvider` before the user callback runs. If that SDK contract were to change (or the test helper were ever reused against a plain plugin), the function would silently throw `"Qwen provider was not registered"`. A comment explaining the SDK wrapping assumption, or a test assertion on `provider !== undefined` before the throw, would make the invariant explicit.

How can I resolve this? If you propose a fix, please make it concise.

@vincentkoc vincentkoc added the clawsweeper Tracked by ClawSweeper automation label Apr 27, 2026
@vincentkoc
vincentkoc force-pushed the clownfish/ghcrawl-156617-autonomous-smoke branch from 4736571 to 566a393 Compare April 28, 2026 05:24
@vincentkoc
vincentkoc force-pushed the clownfish/ghcrawl-156617-autonomous-smoke branch from 566a393 to 40e1e3a Compare April 28, 2026 09:21
@vincentkoc
vincentkoc force-pushed the clownfish/ghcrawl-156617-autonomous-smoke branch 2 times, most recently from 262775b to 8cf1b73 Compare April 28, 2026 09:30
@vincentkoc
vincentkoc force-pushed the clownfish/ghcrawl-156617-autonomous-smoke branch from 8cf1b73 to 41efb98 Compare April 28, 2026 09:33
@vincentkoc

Copy link
Copy Markdown
Member Author

Aisle follow-up reviewed. The inline-model ordering is intentional here, not a suppression bypass: manifest modelCatalog.suppressions filters stale/unsupported built-in catalog rows, while models.providers.<id>.models[] is explicit operator configuration and must be allowed to opt into qwen/qwen3.6-plus on Coding Plan.

The branch now also canonicalizes Coding Plan hosts for manifest suppression and the Qwen catalog path, including whitespace/trailing-dot URLs, so unconfigured built-in rows remain suppressed.

Proof:

  • OPENCLAW_TESTBOX=1 pnpm test:serial src/plugins/manifest-model-suppression.test.ts src/agents/pi-embedded-runner/model.test.ts extensions/qwen/index.test.ts extensions/qwen/provider-catalog.test.ts passed in Testbox.
  • OPENCLAW_TESTBOX=1 pnpm check:changed passed in Testbox.

@vincentkoc
vincentkoc merged commit 058b578 into main Apr 28, 2026
65 of 68 checks passed
@vincentkoc
vincentkoc deleted the clownfish/ghcrawl-156617-autonomous-smoke branch April 28, 2026 09:38

Copy link
Copy Markdown
Contributor

Thanks for landing the narrower fix and for preserving the explicit models.providers.<id>.models[] opt-in path.

I have one follow-up question to better understand the intended policy going forward: why should qwen3.6-plus remain suppressed from the built-in Coding Plan catalog if explicit user configuration is now allowed to resolve it on Coding Plan endpoints?

From a user/configuration perspective, the distinction is a little subtle:

  • built-in catalog entry on Coding Plan: still hidden/suppressed
  • explicit provider model config on Coding Plan: allowed and resolved before suppression

Is the catalog suppression mainly intended as a conservative product signal because Coding Plan availability may be tenant/plan/rollout dependent, while explicit config is treated as an operator opt-in? Or is there another security/runtime reason for keeping the built-in catalog boundary?

Understanding that distinction would help downstream users decide whether they should rely on explicit config only, or whether broader catalog exposure may be considered later once Qwen availability is more uniform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling clawsweeper Tracked by ClawSweeper automation maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants