Skip to content

feat: harden model routes and expose CLI schema#101

Merged
namastex888 merged 1 commit into
mainfrom
drogo/prod-rlmx
May 30, 2026
Merged

feat: harden model routes and expose CLI schema#101
namastex888 merged 1 commit into
mainfrom
drogo/prod-rlmx

Conversation

@namastex888
Copy link
Copy Markdown
Collaborator

@namastex888 namastex888 commented May 30, 2026

Summary

  • add rlmx --schema for machine-readable CLI/schema inspection
  • normalize OpenRouter developer chat roles to system without mutating caller payloads
  • preserve OpenRouter provider-prefixed model ids while de-duplicating native provider refs in logs/traces
  • upgrade @earendil-works/pi-ai to 0.77.0 and add direct Opus 4.8 route tests
  • add env-key recognition for additional providers used by RLMX routes
  • make RLMX_REPL_TIMEOUT_MS robust against invalid/non-positive values

Public safety

  • staged diff only; private/local University arena and fleet policy artifacts intentionally excluded
  • no secrets, credentials, internal URLs, customer data, PII, or private KHAL doctrine in committed diff

Verification

  • npm run build
  • npm run check
  • npm test → 377 pass / 0 fail
  • independent staged-diff review → pass, no security concerns or logic errors
  • static scan → no hardcoded secrets, shell/eval/pickle/SQL risks, or private terms in added lines

Dogfood

Artifacts: /tmp/rlmx-dogfood-20260530T150150Z

  • ./dist/src/cli.js --schema parsed successfully; required flags present (--schema, --context, --output, --stats, --thinking, --cache, --batch-api)
  • RLMX_REPL_TIMEOUT_MS=not-a-number ./dist/src/cli.js ... returned BAD_TIMEOUT_FALLBACK_OK
  • temporarily forced openrouter/deepseek-v4-flash; live smoke returned OPENROUTER_COMPAT_OK with non-zero stats:
    • model: openrouter/deepseek/deepseek-v4-flash
    • total tokens: 646
    • time: 11013ms
    • cost: $0.00015512
  • restored local RLMX global route to anthropic / claude-opus-4-8

Summary by CodeRabbit

  • New Features

    • Added a CLI command to output a machine-readable CLI schema.
    • Improved multi-provider LLM handling with better OpenRouter compatibility.
  • Improvements

    • Model identifiers formatted consistently across CLI, logging, and results.
    • REPL timeout now configurable via environment variable.
    • Expanded API key environment variable support for more providers.
  • Chores

    • Bumped @earendil-works/pi-ai dependency to v0.77.0.
  • Tests

    • Added tests for OpenRouter compatibility, model resolution, schema shape, and API key injection.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f735531f-2698-40ba-8e83-3977f3298be5

📥 Commits

Reviewing files that changed from the base of the PR and between ba2dd98 and 5a4f46e.

⛔ Files ignored due to path filters (16)
  • dist/src/cli.js is excluded by !**/dist/**
  • dist/src/llm.d.ts is excluded by !**/dist/**
  • dist/src/llm.js is excluded by !**/dist/**
  • dist/src/repl.js is excluded by !**/dist/**
  • dist/src/rlm.js is excluded by !**/dist/**
  • dist/src/schema.d.ts is excluded by !**/dist/**
  • dist/src/schema.js is excluded by !**/dist/**
  • dist/src/settings.js is excluded by !**/dist/**
  • dist/tests/openrouter-compat.test.d.ts is excluded by !**/dist/**
  • dist/tests/openrouter-compat.test.js is excluded by !**/dist/**
  • dist/tests/pi-ai-model-support.test.d.ts is excluded by !**/dist/**
  • dist/tests/pi-ai-model-support.test.js is excluded by !**/dist/**
  • dist/tests/schema.test.d.ts is excluded by !**/dist/**
  • dist/tests/schema.test.js is excluded by !**/dist/**
  • dist/tests/settings.test.js is excluded by !**/dist/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • package.json
  • src/cli.ts
  • src/llm.ts
  • src/repl.ts
  • src/rlm.ts
  • src/schema.ts
  • src/settings.ts
  • tests/openrouter-compat.test.ts
  • tests/pi-ai-model-support.test.ts
  • tests/schema.test.ts
  • tests/settings.test.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • package.json
  • src/settings.ts
  • src/repl.ts
  • tests/schema.test.ts
  • tests/settings.test.ts
  • src/schema.ts
  • tests/openrouter-compat.test.ts
  • tests/pi-ai-model-support.test.ts
  • src/rlm.ts
  • src/cli.ts
  • src/llm.ts

📝 Walkthrough

Walkthrough

Adds a machine-readable CLI schema and --schema command, normalization helpers for multi-provider LLM payloads (OpenRouter/model IDs), a composable payloadHooks pipeline, consistent model-ref formatting across logging/traces/results, environment-driven REPL timeout and expanded API-key mappings, tests, and a pi-ai dependency bump.

Changes

CLI Schema and Multi-Provider LLM Enhancements

Layer / File(s) Summary
CLI schema types and content
src/schema.ts
TypeScript interfaces for CLI schema, RLMX_CLI_SCHEMA constant with flags/output/exitCodes, and printRlmxCliSchema() to emit JSON.
CLI schema command wiring
src/cli.ts
Imports and help text added, --schema flag parsed with early-return, CliOptions.command extended, and main() routes schema to printRlmxCliSchema().
CLI schema validation tests
tests/schema.test.ts
Tests RLMX_CLI_SCHEMA flags presence/descriptions, output JSON-schema shape (required answer), and exit code mappings.
LLM normalization and model-id helpers
src/llm.ts
Exports normalizeOpenRouterDeveloperRole, normalizeProviderModelId, and formatModelRef; refactors resolveModel to use normalized IDs and handle kimi-coding special-cases.
Payload hooks and model-resolution pipeline
src/llm.ts, tests/pi-ai-model-support.test.ts
Replaces single Gemini onPayload with payloadHooks pipeline sequencing async transformers; adds tests for pi-ai Opus 4.8 resolution.
OpenRouter compatibility tests
tests/openrouter-compat.test.ts
Tests developer→system role normalization, provider/model id normalization behavior (including openrouter prefix preservation), and immutability.
Consistent model-ref formatting throughout
src/cli.ts, src/rlm.ts
Replaces ${provider}/${model} with formatModelRef(...) in logger runStart, session save payloads, Langfuse traces, iteration/generation starts, forced-final call, and result model field.
REPL timeout and API key env support
src/repl.ts, src/settings.ts, tests/settings.test.ts
Adds defaultReplTimeoutMs() reading RLMX_REPL_TIMEOUT_MS and uses it in REPL.execute; expands ENV_KEY_MAP with additional provider API keys and tests injectApiKeysToEnv.
Dependency version update
package.json
Bumps @earendil-works/pi-ai from 0.75.50.77.0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • automagik-dev/rlmx#37: Both PRs modify core LLM plumbing (src/llm.ts, resolveModel, llmComplete) and related call sites.

Poem

🐰 A schema printed, tidy and neat,

Models normalized, roles discrete.
Hooks chain payloads, timeouts obey,
Keys find their env and tests hold sway.
This rabbit hops—your CLI's complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: harden model routes and expose CLI schema' directly summarizes the main changes: hardening model handling/routes and exposing a new CLI schema feature via the --schema flag.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch drogo/prod-rlmx

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request upgrades the @earendil-works/pi-ai dependency to 0.77.0, introduces a machine-readable CLI schema with a new --schema flag, adds OpenRouter compatibility payload normalization (converting the developer role to system), and implements provider/model ID normalization. It also adds environment variable injection for several new API keys and configures a customizable REPL timeout. The review feedback highlights two important issues in src/llm.ts: first, the onPayload hook wrapper is defined as async but should be synchronous to match pi-ai expectations; second, the fallback check for kimi-coding should use normalizedModelId instead of modelId to prevent failures when provider prefixes are present.

Comment thread src/llm.ts
Comment on lines +249 to +283
const payloadHooks: Array<(payload: unknown, model: unknown) => unknown | Promise<unknown>> = [];

// OpenRouter's OpenAI-compatible Chat Completions endpoint still rejects the
// newer `developer` role for several non-OpenAI routes (including DeepSeek
// V4 Flash). pi-ai may emit `developer` for reasoning models, so normalize
// it back to `system` at the RLMX boundary instead of letting the provider
// fail with an empty/zero-token response.
if (modelConfig.provider === "openrouter") {
payloadHooks.push(normalizeOpenRouterDeveloperRole);
}

// Build onPayload hook for Gemini-specific features (media resolution, structured outputs, tools, etc.)
if (isGoogleProvider(modelConfig.provider) && options?.geminiConfig) {
const onPayload = buildGeminiOnPayload(
const geminiOnPayload = buildGeminiOnPayload(
options.geminiConfig,
modelConfig.provider,
options?.outputSchema
);
if (onPayload) {
piOptions.onPayload = onPayload;
if (geminiOnPayload) {
payloadHooks.push(geminiOnPayload as (payload: unknown, model: unknown) => unknown | Promise<unknown>);
}
}

if (payloadHooks.length > 0) {
piOptions.onPayload = async (payload: unknown, payloadModel: unknown) => {
let next = payload;
for (const hook of payloadHooks) {
const result = await hook(next, payloadModel);
if (result !== undefined) {
next = result;
}
}
return next;
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The onPayload hook wrapper is currently defined as async and returns a Promise. Since pi-ai expects a synchronous payload modification callback, returning a Promise will cause the library to receive a Promise object instead of the modified payload, leading to serialization failures or runtime crashes. Since all registered hooks are entirely synchronous, the wrapper should be synchronous as well.

  const payloadHooks: Array<(payload: unknown, model: unknown) => unknown> = [];

  // OpenRouter's OpenAI-compatible Chat Completions endpoint still rejects the
  // newer `developer` role for several non-OpenAI routes (including DeepSeek
  // V4 Flash). pi-ai may emit `developer` for reasoning models, so normalize
  // it back to `system` at the RLMX boundary instead of letting the provider
  // fail with an empty/zero-token response.
  if (modelConfig.provider === "openrouter") {
    payloadHooks.push(normalizeOpenRouterDeveloperRole);
  }

  // Build onPayload hook for Gemini-specific features (media resolution, structured outputs, tools, etc.)
  if (isGoogleProvider(modelConfig.provider) && options?.geminiConfig) {
    const geminiOnPayload = buildGeminiOnPayload(
      options.geminiConfig,
      modelConfig.provider,
      options?.outputSchema
    );
    if (geminiOnPayload) {
      payloadHooks.push(geminiOnPayload as (payload: unknown, model: unknown) => unknown);
    }
  }

  if (payloadHooks.length > 0) {
    piOptions.onPayload = (payload: unknown, payloadModel: unknown) => {
      let next = payload;
      for (const hook of payloadHooks) {
        const result = hook(next, payloadModel);
        if (result !== undefined) {
          next = result;
        }
      }
      return next;
    };
  }

Comment thread src/llm.ts
Comment on lines +153 to +168
if (!model && provider === "kimi-coding" && modelId.startsWith("kimi-k2.6")) {
// pi-ai 0.77.0's generated registry exposes Kimi's coding endpoint but has
// not caught up with K2.6 under the `kimi-coding` provider. The endpoint
// accepts K2.6 model IDs with the same Anthropic-compatible transport and
// KIMI_API_KEY auth as `kimi-k2-thinking`, so clone that route rather than
// falling back to Moonshot API credentials we do not have.
const template = getModel("kimi-coding" as KnownProvider, "kimi-k2-thinking" as never);
if (template) {
model = {
...template,
id: modelId,
name: "Kimi K2.6",
input: ["text", "image"],
} as typeof template;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The fallback check and model ID assignment for kimi-coding currently use modelId instead of normalizedModelId. If the model is specified with a provider prefix (e.g., kimi-coding/kimi-k2.6), modelId.startsWith("kimi-k2.6") will evaluate to false, preventing the fallback from triggering. Additionally, setting id: modelId would pass the provider-prefixed ID to the upstream API, which will fail. Using normalizedModelId resolves both issues.

Suggested change
if (!model && provider === "kimi-coding" && modelId.startsWith("kimi-k2.6")) {
// pi-ai 0.77.0's generated registry exposes Kimi's coding endpoint but has
// not caught up with K2.6 under the `kimi-coding` provider. The endpoint
// accepts K2.6 model IDs with the same Anthropic-compatible transport and
// KIMI_API_KEY auth as `kimi-k2-thinking`, so clone that route rather than
// falling back to Moonshot API credentials we do not have.
const template = getModel("kimi-coding" as KnownProvider, "kimi-k2-thinking" as never);
if (template) {
model = {
...template,
id: modelId,
name: "Kimi K2.6",
input: ["text", "image"],
} as typeof template;
}
}
if (!model && provider === "kimi-coding" && normalizedModelId.startsWith("kimi-k2.6")) {
// pi-ai 0.77.0's generated registry exposes Kimi's coding endpoint but has
// not caught up with K2.6 under the `kimi-coding` provider. The endpoint
// accepts K2.6 model IDs with the same Anthropic-compatible transport and
// KIMI_API_KEY auth as `kimi-k2-thinking`, so clone that route rather than
// falling back to Moonshot API credentials we do not have.
const template = getModel("kimi-coding" as KnownProvider, "kimi-k2-thinking" as never);
if (template) {
model = {
...template,
id: normalizedModelId,
name: "Kimi K2.6",
input: ["text", "image"],
} as typeof template;
}
}

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba2dd98a43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/llm.ts
model = getModel(provider as KnownProvider, stripped as never);
}
}
if (!model && provider === "kimi-coding" && modelId.startsWith("kimi-k2.6")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize Kimi K2.6 before applying fallback

When users pass the direct-provider form that this helper now accepts elsewhere, e.g. provider: kimi-coding with model: kimi-coding/kimi-k2.6, normalizedModelId becomes kimi-k2.6 but this fallback still tests the raw modelId. Because pi-ai does not resolve K2.6 and this branch is skipped, the model throws as unknown even though the prefix normalization was intended to make prefixed native model IDs work; use the normalized ID for the Kimi K2.6 check and cloned model id.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/rlm.ts (1)

232-232: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use formatModelRef here too for consistent model strings.

The Langfuse paths now use formatModelRef(...), but the ObservabilityRecorder calls at Line 232 (startSession) and Line 457 (recordLLMCall) still build the raw ${config.model.provider}/${config.model.model} string. For a config whose model already carries a native provider prefix (the exact case normalizeProviderModelId exists to handle), these emit a duplicated prefix (e.g. deepseek/deepseek/deepseek-v4-pro), diverging from the Langfuse trace value. Route both through formatModelRef for consistent observability data. Same fix applies at Line 457.

🔧 Proposed fix (Line 232)
-      `${config.model.provider}/${config.model.model}`,
+      formatModelRef(config.model.provider, config.model.model),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rlm.ts` at line 232, Replace the ad-hoc model string construction with
the canonical formatter: update the ObservabilityRecorder invocations
(startSession and recordLLMCall) that currently pass
`${config.model.provider}/${config.model.model}` to instead call
formatModelRef(config.model) so the model reference matches normalized
provider-prefixed ids; locate the calls on startSession and recordLLMCall and
swap the raw template string with formatModelRef(config.model).
🧹 Nitpick comments (2)
tests/settings.test.ts (1)

97-131: 💤 Low value

Optional: also cover the "env takes priority" branch.

The new block verifies injection when vars are unset, but not that injectApiKeysToEnv leaves an already-set env var untouched (the !process.env[envVar] guard). Adding one assertion would lock in that contract.

♻️ Optional addition
   it("injects cheap/diverse provider keys from settings", () => {
     injectApiKeysToEnv({
       DEEPSEEK_API_KEY: "deepseek-test-key",
       KIMI_API_KEY: "kimi-test-key",
       MINIMAX_API_KEY: "minimax-test-key",
       ZAI_API_KEY: "zai-test-key",
       GOOGLE_API_KEY: "google-test-key",
     });

     assert.equal(process.env.DEEPSEEK_API_KEY, "deepseek-test-key");
     assert.equal(process.env.KIMI_API_KEY, "kimi-test-key");
     assert.equal(process.env.MINIMAX_API_KEY, "minimax-test-key");
     assert.equal(process.env.ZAI_API_KEY, "zai-test-key");
     assert.equal(process.env.GOOGLE_API_KEY, "google-test-key");
   });
+
+  it("does not override an already-set env var", () => {
+    process.env.DEEPSEEK_API_KEY = "preexisting";
+    injectApiKeysToEnv({ DEEPSEEK_API_KEY: "from-settings" });
+    assert.equal(process.env.DEEPSEEK_API_KEY, "preexisting");
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/settings.test.ts` around lines 97 - 131, Add a test case to verify the
"env takes priority" branch by pre-setting one of the env vars (e.g.,
process.env.DEEPSEEK_API_KEY = "already-set") before calling injectApiKeysToEnv
and asserting that injectApiKeysToEnv does not overwrite it
(process.env.DEEPSEEK_API_KEY remains "already-set"), while still injecting the
other keys; reference the existing test block and the injectApiKeysToEnv
function and reuse the originals map/cleanup in before/after so the test does
not leak state.
src/llm.ts (1)

153-166: 💤 Low value

Use the normalized model id in the kimi-coding special case.

The lookup at Line 145 and date-suffix strip at Line 148 operate on normalizedModelId, but the kimi-coding branch keys off raw modelId (startsWith("kimi-k2.6") and id: modelId). If a kimi-coding model is ever configured with the provider prefix (e.g. kimi-coding/kimi-k2.6-...), the prefix is stripped for resolution but the startsWith check fails, so the clone path is skipped and resolveModel throws. Aligning on normalizedModelId keeps this consistent with the normalization helper added in this PR.

♻️ Proposed consistency fix
-  if (!model && provider === "kimi-coding" && modelId.startsWith("kimi-k2.6")) {
+  if (!model && provider === "kimi-coding" && normalizedModelId.startsWith("kimi-k2.6")) {
     ...
     if (template) {
       model = {
         ...template,
-        id: modelId,
+        id: normalizedModelId,
         name: "Kimi K2.6",
         input: ["text", "image"],
       } as typeof template;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llm.ts` around lines 153 - 166, The kimi-coding special-case should use
the normalizedModelId rather than raw modelId: change the conditional that
checks startsWith("kimi-k2.6") to operate on normalizedModelId and set model.id
to normalizedModelId when cloning the template returned by
getModel("kimi-coding", "kimi-k2-thinking"); update references to ensure the
date-suffix stripping/lookup behavior uses normalizedModelId consistently
(symbols: normalizedModelId, modelId, provider, getModel, model).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/schema.ts`:
- Around line 36-199: The schema is missing benchmark-only flags and
under-scopes --output; update the flags array to add entries for "--samples"
(type: number, default: 1, description: "Number of sample runs for benchmark
mode", appliesTo: ["benchmark"]) and "--idx" (type: number, default: 0,
description: "Start index for benchmark iterations", appliesTo: ["benchmark"]),
and modify the existing "--output" flag's appliesTo to include "benchmark"
(appliesTo: ["query", "benchmark"]) so runBenchmarkCommand and tooling recognize
and accept these options.

---

Outside diff comments:
In `@src/rlm.ts`:
- Line 232: Replace the ad-hoc model string construction with the canonical
formatter: update the ObservabilityRecorder invocations (startSession and
recordLLMCall) that currently pass
`${config.model.provider}/${config.model.model}` to instead call
formatModelRef(config.model) so the model reference matches normalized
provider-prefixed ids; locate the calls on startSession and recordLLMCall and
swap the raw template string with formatModelRef(config.model).

---

Nitpick comments:
In `@src/llm.ts`:
- Around line 153-166: The kimi-coding special-case should use the
normalizedModelId rather than raw modelId: change the conditional that checks
startsWith("kimi-k2.6") to operate on normalizedModelId and set model.id to
normalizedModelId when cloning the template returned by getModel("kimi-coding",
"kimi-k2-thinking"); update references to ensure the date-suffix
stripping/lookup behavior uses normalizedModelId consistently (symbols:
normalizedModelId, modelId, provider, getModel, model).

In `@tests/settings.test.ts`:
- Around line 97-131: Add a test case to verify the "env takes priority" branch
by pre-setting one of the env vars (e.g., process.env.DEEPSEEK_API_KEY =
"already-set") before calling injectApiKeysToEnv and asserting that
injectApiKeysToEnv does not overwrite it (process.env.DEEPSEEK_API_KEY remains
"already-set"), while still injecting the other keys; reference the existing
test block and the injectApiKeysToEnv function and reuse the originals
map/cleanup in before/after so the test does not leak state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c5ad04a-0b3d-4449-bb7e-a54cb294d335

📥 Commits

Reviewing files that changed from the base of the PR and between 456a574 and ba2dd98.

⛔ Files ignored due to path filters (18)
  • dist/src/cli.js is excluded by !**/dist/**
  • dist/src/llm.d.ts is excluded by !**/dist/**
  • dist/src/llm.js is excluded by !**/dist/**
  • dist/src/repl.js is excluded by !**/dist/**
  • dist/src/rlm.js is excluded by !**/dist/**
  • dist/src/schema.d.ts is excluded by !**/dist/**
  • dist/src/schema.js is excluded by !**/dist/**
  • dist/src/settings.js is excluded by !**/dist/**
  • dist/src/version.d.ts is excluded by !**/dist/**
  • dist/src/version.js is excluded by !**/dist/**
  • dist/tests/openrouter-compat.test.d.ts is excluded by !**/dist/**
  • dist/tests/openrouter-compat.test.js is excluded by !**/dist/**
  • dist/tests/pi-ai-model-support.test.d.ts is excluded by !**/dist/**
  • dist/tests/pi-ai-model-support.test.js is excluded by !**/dist/**
  • dist/tests/schema.test.d.ts is excluded by !**/dist/**
  • dist/tests/schema.test.js is excluded by !**/dist/**
  • dist/tests/settings.test.js is excluded by !**/dist/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • package.json
  • src/cli.ts
  • src/llm.ts
  • src/repl.ts
  • src/rlm.ts
  • src/schema.ts
  • src/settings.ts
  • tests/openrouter-compat.test.ts
  • tests/pi-ai-model-support.test.ts
  • tests/schema.test.ts
  • tests/settings.test.ts

Comment thread src/schema.ts
Comment on lines +36 to +199
flags: [
{
name: "--schema",
type: "boolean",
default: false,
description: "Print this machine-readable CLI schema as JSON and exit.",
},
{
name: "--context",
type: "string",
default: null,
description: "Path to context directory or file loaded for a query, cache warmup, or batch run.",
appliesTo: ["query", "cache", "batch"],
},
{
name: "--output",
type: "string",
default: "text",
choices: ["text", "json", "stream"],
description: "Output mode. json emits a single JSON object; stream emits JSONL iteration/final events.",
appliesTo: ["query"],
},
{
name: "--verbose",
type: "boolean",
default: false,
description: "Show iteration progress and diagnostic messages on stderr.",
},
{
name: "--max-iterations",
type: "number",
default: 30,
description: "Maximum RLM iterations for query or batch runs.",
appliesTo: ["query", "batch"],
},
{
name: "--timeout",
type: "number",
default: 300000,
description: "Timeout in milliseconds for query, cache, or batch execution.",
},
{
name: "--dir",
type: "string",
default: "current working directory",
description: "Directory for the init command or config discovery.",
appliesTo: ["init"],
},
{
name: "--help",
aliases: ["-h"],
type: "boolean",
default: false,
description: "Show help text and exit.",
},
{
name: "--version",
aliases: ["-v"],
type: "boolean",
default: false,
description: "Show rlmx version and exit.",
},
{
name: "--stats",
type: "boolean",
default: false,
description: "Emit JSON stats to stderr, or include stats in --output json responses.",
appliesTo: ["query"],
},
{
name: "--log",
type: "string",
default: null,
description: "Write structured JSONL run logs to the given path.",
appliesTo: ["query"],
},
{
name: "--tools",
type: "string",
default: null,
choices: ["core", "standard", "full"],
description: "Tool level exposed to the RLM runtime.",
appliesTo: ["query", "cache", "batch", "benchmark"],
},
{
name: "--max-cost",
type: "number",
default: null,
description: "Maximum USD spend per run.",
appliesTo: ["query", "batch"],
},
{
name: "--max-tokens",
type: "number",
default: null,
description: "Maximum total tokens per run.",
appliesTo: ["query", "batch"],
},
{
name: "--max-depth",
type: "number",
default: null,
description: "Maximum recursive rlm_query depth.",
appliesTo: ["query", "batch"],
},
{
name: "--ext",
type: "list",
default: null,
description: "Comma-separated file extensions for context directories.",
appliesTo: ["query", "cache", "batch"],
},
{
name: "--thinking",
type: "string",
default: null,
choices: ["minimal", "low", "medium", "high"],
description: "Gemini 3 thinking level override.",
appliesTo: ["query"],
},
{
name: "--cache",
type: "boolean",
default: false,
description: "Enable cache mode, injecting full context into the system prompt for provider caching.",
appliesTo: ["query", "batch"],
},
{
name: "--no-session",
type: "boolean",
default: false,
description: "Disable automatic session persistence after query runs.",
appliesTo: ["query"],
},
{
name: "--estimate",
type: "boolean",
default: false,
description: "Estimate context size and cost without warming cache.",
appliesTo: ["cache"],
},
{
name: "--parallel",
type: "number",
default: 1,
description: "Number of concurrent questions for the batch command.",
appliesTo: ["batch"],
},
{
name: "--batch-api",
type: "boolean",
default: false,
description: "Use Gemini Batch API for batch runs where available.",
appliesTo: ["batch"],
},
{
name: "--template",
type: "string",
default: "default",
choices: ["default", "code"],
description: "Template used by the init command.",
appliesTo: ["init"],
},
],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Schema omits benchmark-only flags --samples/--idx, and --output is under-scoped.

runBenchmarkCommand parses --samples and --idx (cli.ts Lines 782-785) and consumes --output json for the cost mode (Lines 771-772), but this "complete" machine-readable schema doesn't list --samples/--idx and --output.appliesTo is limited to ["query"]. Tooling relying on the schema will treat these as unknown/unsupported.

📝 Suggested additions
       choices: ["text", "json", "stream"],
       description: "Output mode. json emits a single JSON object; stream emits JSONL iteration/final events.",
-      appliesTo: ["query"],
+      appliesTo: ["query", "benchmark"],
     },

Add entries for the benchmark flags (placement is illustrative):

+    {
+      name: "--samples",
+      type: "number",
+      default: 5,
+      description: "Number of Oolong samples to run.",
+      appliesTo: ["benchmark"],
+    },
+    {
+      name: "--idx",
+      type: "number",
+      default: null,
+      description: "Run a specific Oolong sample by index.",
+      appliesTo: ["benchmark"],
+    },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/schema.ts` around lines 36 - 199, The schema is missing benchmark-only
flags and under-scopes --output; update the flags array to add entries for
"--samples" (type: number, default: 1, description: "Number of sample runs for
benchmark mode", appliesTo: ["benchmark"]) and "--idx" (type: number, default:
0, description: "Start index for benchmark iterations", appliesTo:
["benchmark"]), and modify the existing "--output" flag's appliesTo to include
"benchmark" (appliesTo: ["query", "benchmark"]) so runBenchmarkCommand and
tooling recognize and accept these options.

Verified-by: Drogo pre-commit review, npm run build, npm run check, npm test, live CLI dogfood.
@namastex888 namastex888 merged commit 0b6a0e2 into main May 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants