From d3f132d694bf56a0e8489dfb448e65034543e9df Mon Sep 17 00:00:00 2001 From: Elia <83713217+eliahilse@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:26:15 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20glm=20engine=20=E2=80=94=20GLM-5.2?= =?UTF-8?q?=20on=20the=20Z.ai=20coding=20plan=20via=20Claude=20Code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opencode's Z.ai integration deadlocks on GLM's streamed tool-call deltas (anomalyco/opencode#6708, #8428), so the engine rides the claude binary against Z.ai's Anthropic-compatible endpoint instead — same subscription quota, and the probe/deny execution policy applies. The key comes from ZAI_API_KEY or opencode's auth store. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na --- README.md | 1 + packages/review/cli/README.md | 1 + packages/review/cli/src/engines.ts | 32 +++++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 525c25e..b0e428d 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Findings cluster by file, overlapping lines, and description similarity, so five | `codex` | `codex exec` (read-only sandbox) | `codex login` (ChatGPT sub) or `OPENAI_API_KEY` | | `claude` | `claude -p` (probes allowed, writes and CI suites denied) | `claude` login or `CLAUDE_CODE_OAUTH_TOKEN` | | `kimi` | Claude Code against Kimi's Anthropic-compatible endpoint | `KIMI_API_KEY` | +| `glm` | GLM-5.2 via Claude Code against Z.ai's Anthropic-compatible endpoint | `ZAI_API_KEY`, or picked up from `opencode auth login` | | `grok` | `grok -p` (grok-4.5, high reasoning effort) | `grok login` (SuperGrok / X Premium+) or `GROK_API_KEY` | | `qwen` | `qwen -p` | `qwen` login (Coding Plan) or API key | diff --git a/packages/review/cli/README.md b/packages/review/cli/README.md index 0f7796e..0c384e9 100644 --- a/packages/review/cli/README.md +++ b/packages/review/cli/README.md @@ -30,6 +30,7 @@ bunx @kyora-sh/review review --pr 123 --post --verify | `codex` | `codex exec` (read-only sandbox) | `codex login` (ChatGPT sub) or `OPENAI_API_KEY` | | `claude` | `claude -p` (probes allowed, writes and CI suites denied) | `claude` login or `CLAUDE_CODE_OAUTH_TOKEN` | | `kimi` | Claude Code against Kimi's Anthropic-compatible endpoint | `KIMI_API_KEY` (+ optional `KIMI_BASE_URL`, `KIMI_MODEL`) | +| `glm` | GLM-5.2 via Claude Code against Z.ai's Anthropic-compatible endpoint | `ZAI_API_KEY` or `opencode auth login` | | `grok` | `grok -p` | `grok` login or `GROK_API_KEY` / `XAI_API_KEY` | | `qwen` | `qwen -p` | `qwen` login (Coding Plan) or API key | diff --git a/packages/review/cli/src/engines.ts b/packages/review/cli/src/engines.ts index 8a54ac7..abb1599 100644 --- a/packages/review/cli/src/engines.ts +++ b/packages/review/cli/src/engines.ts @@ -1,5 +1,6 @@ +import { readFileSync } from "node:fs" import { mkdtemp, rm } from "node:fs/promises" -import { tmpdir } from "node:os" +import { homedir, tmpdir } from "node:os" import { join } from "node:path" import type { EngineOverride, ReviewConfig } from "./types" @@ -9,6 +10,8 @@ export interface EngineDef { bin: string /** env vars that must all be present for this engine to be selectable */ requiresEnv?: string[] + /** extra readiness check; returns a reason when unavailable, null when ready */ + ready?: () => string | null /** tokens {prompt} {schema} {out} are substituted; first element is replaced by the resolved bin */ args: string[] env?: () => Record @@ -17,6 +20,17 @@ export interface EngineDef { authHint: string } +function zaiKey(): string | undefined { + if (process.env.ZAI_API_KEY) return process.env.ZAI_API_KEY + try { + const auth = JSON.parse(readFileSync(join(homedir(), ".local/share/opencode/auth.json"), "utf8")) + const entry = auth["zai-coding-plan"] + return entry?.key ?? entry?.apiKey ?? undefined + } catch { + return undefined + } +} + const CLAUDE_DENIED = [ "Write", "Edit", "MultiEdit", "NotebookEdit", "WebFetch", "WebSearch", "Bash(bun test:*)", "Bash(bun run:*)", "Bash(bun install:*)", "Bash(bunx turbo:*)", @@ -71,6 +85,20 @@ export const ENGINES: EngineDef[] = [ }), authHint: "set KIMI_API_KEY (Kimi membership / platform.kimi.ai); optional KIMI_BASE_URL, KIMI_MODEL", }, + { + id: "glm", + label: "GLM 5.2 (Z.ai, via Claude Code)", + bin: "claude", + ready: () => (zaiKey() ? null : "no Z.ai key (set ZAI_API_KEY or run `opencode auth login` → Z.AI Coding Plan)"), + args: CLAUDE_ARGS, + env: () => ({ + ANTHROPIC_BASE_URL: process.env.ZAI_BASE_URL ?? "https://api.z.ai/api/anthropic", + ANTHROPIC_AUTH_TOKEN: zaiKey(), + ANTHROPIC_MODEL: process.env.ZAI_MODEL ?? "glm-5.2", + ANTHROPIC_API_KEY: undefined, + }), + authHint: "set ZAI_API_KEY (GLM Coding Plan), or log in once via `opencode auth login` — the key is picked up from there", + }, { id: "grok", label: "Grok Build (xAI)", @@ -107,6 +135,8 @@ export function engineStatus(engine: EngineDef, override: EngineOverride | undef return { engine, available: false, reason: `${name} not set` } } } + const notReady = engine.ready?.() + if (notReady) return { engine, available: false, reason: notReady } return { engine, available: true, reason: "ready" } } From bd2b8336986e7e16ac39f64b27e390d66b57d255 Mon Sep 17 00:00:00 2001 From: Elia <83713217+eliahilse@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:45:01 +0200 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20qwen=20engine=20=E2=80=94=20qwen3.8?= =?UTF-8?q?-max-preview=20on=20the=20Token=20Plan=20via=20Claude=20Code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opencode's agentic loop proved unreliable locally regardless of provider (fresh-leader runs wedge on any tool-triggering prompt), so qwen rides the claude binary against the Token Plan's Anthropic endpoint, replacing the unvalidated qwen-code adapter. Key resolves from QWEN_API_KEY or the config bl config agent writes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na --- README.md | 2 +- packages/review/cli/README.md | 2 +- packages/review/cli/src/engines.ts | 26 ++++++++++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b0e428d..e9f502e 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Findings cluster by file, overlapping lines, and description similarity, so five | `kimi` | Claude Code against Kimi's Anthropic-compatible endpoint | `KIMI_API_KEY` | | `glm` | GLM-5.2 via Claude Code against Z.ai's Anthropic-compatible endpoint | `ZAI_API_KEY`, or picked up from `opencode auth login` | | `grok` | `grok -p` (grok-4.5, high reasoning effort) | `grok login` (SuperGrok / X Premium+) or `GROK_API_KEY` | -| `qwen` | `qwen -p` | `qwen` login (Coding Plan) or API key | +| `qwen` | Qwen (default qwen3.8-max-preview) via Claude Code against the Token Plan Anthropic endpoint | `QWEN_API_KEY` or picked up from `bl config agent` | Every available engine runs by default; pick explicitly with `--engines codex,kimi`. An engine that's missing, rate-limited, or failing drops out and the review still lands with the rest. If *every* engine fails, the run exits non-zero instead of reporting a clean review. diff --git a/packages/review/cli/README.md b/packages/review/cli/README.md index 0c384e9..f9b7dcf 100644 --- a/packages/review/cli/README.md +++ b/packages/review/cli/README.md @@ -32,7 +32,7 @@ bunx @kyora-sh/review review --pr 123 --post --verify | `kimi` | Claude Code against Kimi's Anthropic-compatible endpoint | `KIMI_API_KEY` (+ optional `KIMI_BASE_URL`, `KIMI_MODEL`) | | `glm` | GLM-5.2 via Claude Code against Z.ai's Anthropic-compatible endpoint | `ZAI_API_KEY` or `opencode auth login` | | `grok` | `grok -p` | `grok` login or `GROK_API_KEY` / `XAI_API_KEY` | -| `qwen` | `qwen -p` | `qwen` login (Coding Plan) or API key | +| `qwen` | Qwen (default qwen3.8-max-preview) via Claude Code against the Token Plan Anthropic endpoint | `QWEN_API_KEY` or `bl config agent` | By default every available engine runs; pick explicitly with `--engines codex,kimi`. An engine that's rate-limited or fails just drops out — the review still lands with the rest. diff --git a/packages/review/cli/src/engines.ts b/packages/review/cli/src/engines.ts index abb1599..5d046d5 100644 --- a/packages/review/cli/src/engines.ts +++ b/packages/review/cli/src/engines.ts @@ -20,6 +20,16 @@ export interface EngineDef { authHint: string } +function bailianKey(): string | undefined { + if (process.env.QWEN_API_KEY) return process.env.QWEN_API_KEY + try { + const config = JSON.parse(readFileSync(join(homedir(), ".config/opencode/opencode.json"), "utf8")) + return config.provider?.["bailian-cli"]?.options?.apiKey ?? undefined + } catch { + return undefined + } +} + function zaiKey(): string | undefined { if (process.env.ZAI_API_KEY) return process.env.ZAI_API_KEY try { @@ -108,10 +118,18 @@ export const ENGINES: EngineDef[] = [ }, { id: "qwen", - label: "Qwen Code (Alibaba)", - bin: "qwen", - args: ["-p", "{prompt}"], - authHint: "run `qwen` once to log in (Coding Plan), or configure an API key per qwen-code docs", + label: "Qwen (Alibaba Token Plan, via Claude Code)", + bin: "claude", + ready: () => (bailianKey() ? null : "no Token Plan key (set QWEN_API_KEY or run `bl config agent --agent opencode ...`)"), + args: CLAUDE_ARGS, + env: () => ({ + ANTHROPIC_BASE_URL: + process.env.QWEN_BASE_URL ?? "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic", + ANTHROPIC_AUTH_TOKEN: bailianKey(), + ANTHROPIC_MODEL: process.env.QWEN_MODEL ?? "qwen3.8-max-preview", + ANTHROPIC_API_KEY: undefined, + }), + authHint: "set QWEN_API_KEY (Token Plan key), or run `bl config agent` once — the key is picked up from there", }, ] From 2ddc254eca8a13707bfb72e66a99a04a115a0141 Mon Sep 17 00:00:00 2001 From: Elia <83713217+eliahilse@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:12:47 +0200 Subject: [PATCH 3/3] fix: action runs bundled source by default, no npm required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review CLI has zero runtime dependencies, so the action executes it straight from its own checkout — uses: @ref pins the version. npm remains an opt-in via the version input. Also carries the glm and qwen engines stranded on feat/exec-probes by the #5 squash timing. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na --- action/README.md | 2 +- action/action.yml | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/action/README.md b/action/README.md index 724a95b..c5b54f6 100644 --- a/action/README.md +++ b/action/README.md @@ -63,7 +63,7 @@ Every engine uses *your* account through *its vendor's own CLI* — that's what | `post` | `true` | post as PR review (inline comments where the finding anchors to the diff) | | `fail-on` | `none` | fail the job at/above a severity: `critical`, `major`, `minor`, `nit` | | `persist-auth` | `true` | cache rotated credentials between runs | -| `version` | `latest` | `@kyora-sh/review` version | +| `version` | `bundled` | run the source shipped with the action (pinned by your `@ref`), or an npm version | ## Security notes diff --git a/action/action.yml b/action/action.yml index 5beda2c..4bd96d1 100644 --- a/action/action.yml +++ b/action/action.yml @@ -21,8 +21,8 @@ inputs: description: "Cache refreshed engine credentials between runs so seeded tokens keep working after they rotate (see README for the public-repo caveat)" default: "true" version: - description: "@kyora-sh/review version to run" - default: "latest" + description: "Which @kyora-sh/review to run: 'bundled' uses the source shipped with the action (pinned by your uses: @ref), or an npm version/tag" + default: "bundled" runs: using: "composite" @@ -91,7 +91,11 @@ runs: [ "${{ inputs.post }}" = "true" ] && args+=(--post) fi [ "${{ inputs.verify }}" = "true" ] && args+=(--verify) - bunx "@kyora-sh/review@${{ inputs.version }}" "${args[@]}" + if [ "${{ inputs.version }}" = "bundled" ]; then + bun "${GITHUB_ACTION_PATH}/../packages/review/cli/src/index.ts" "${args[@]}" + else + bunx "@kyora-sh/review@${{ inputs.version }}" "${args[@]}" + fi - name: Persist refreshed credentials if: always() && inputs.persist-auth == 'true' && steps.setup.outputs.engines != ''