diff --git a/README.md b/README.md index 525c25e..e9f502e 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,9 @@ 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 | +| `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/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 != '' diff --git a/packages/review/cli/README.md b/packages/review/cli/README.md index 0f7796e..f9b7dcf 100644 --- a/packages/review/cli/README.md +++ b/packages/review/cli/README.md @@ -30,8 +30,9 @@ 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 | +| `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 8a54ac7..5d046d5 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,27 @@ 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 { + 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 +95,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)", @@ -80,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", }, ] @@ -107,6 +153,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" } }