feat: harden model routes and expose CLI schema#101
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (16)
📒 Files selected for processing (11)
🚧 Files skipped from review as they are similar to previous changes (11)
📝 WalkthroughWalkthroughAdds a machine-readable CLI schema and ChangesCLI Schema and Multi-Provider LLM Enhancements
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
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.
| 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; | ||
| }; | ||
| } |
There was a problem hiding this comment.
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;
};
}| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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; | |
| } | |
| } |
There was a problem hiding this comment.
💡 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".
| model = getModel(provider as KnownProvider, stripped as never); | ||
| } | ||
| } | ||
| if (!model && provider === "kimi-coding" && modelId.startsWith("kimi-k2.6")) { |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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 winUse
formatModelRefhere too for consistent model strings.The Langfuse paths now use
formatModelRef(...), but theObservabilityRecordercalls at Line 232 (startSession) and Line 457 (recordLLMCall) still build the raw${config.model.provider}/${config.model.model}string. For a config whosemodelalready carries a native provider prefix (the exact casenormalizeProviderModelIdexists to handle), these emit a duplicated prefix (e.g.deepseek/deepseek/deepseek-v4-pro), diverging from the Langfuse trace value. Route both throughformatModelReffor 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 valueOptional: also cover the "env takes priority" branch.
The new block verifies injection when vars are unset, but not that
injectApiKeysToEnvleaves 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 valueUse 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 rawmodelId(startsWith("kimi-k2.6")andid: modelId). If akimi-codingmodel is ever configured with the provider prefix (e.g.kimi-coding/kimi-k2.6-...), the prefix is stripped for resolution but thestartsWithcheck fails, so the clone path is skipped andresolveModelthrows. Aligning onnormalizedModelIdkeeps 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
⛔ Files ignored due to path filters (18)
dist/src/cli.jsis excluded by!**/dist/**dist/src/llm.d.tsis excluded by!**/dist/**dist/src/llm.jsis excluded by!**/dist/**dist/src/repl.jsis excluded by!**/dist/**dist/src/rlm.jsis excluded by!**/dist/**dist/src/schema.d.tsis excluded by!**/dist/**dist/src/schema.jsis excluded by!**/dist/**dist/src/settings.jsis excluded by!**/dist/**dist/src/version.d.tsis excluded by!**/dist/**dist/src/version.jsis excluded by!**/dist/**dist/tests/openrouter-compat.test.d.tsis excluded by!**/dist/**dist/tests/openrouter-compat.test.jsis excluded by!**/dist/**dist/tests/pi-ai-model-support.test.d.tsis excluded by!**/dist/**dist/tests/pi-ai-model-support.test.jsis excluded by!**/dist/**dist/tests/schema.test.d.tsis excluded by!**/dist/**dist/tests/schema.test.jsis excluded by!**/dist/**dist/tests/settings.test.jsis excluded by!**/dist/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
package.jsonsrc/cli.tssrc/llm.tssrc/repl.tssrc/rlm.tssrc/schema.tssrc/settings.tstests/openrouter-compat.test.tstests/pi-ai-model-support.test.tstests/schema.test.tstests/settings.test.ts
| 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"], | ||
| }, | ||
| ], |
There was a problem hiding this comment.
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.
ba2dd98 to
5a4f46e
Compare
Summary
rlmx --schemafor machine-readable CLI/schema inspectiondeveloperchat roles tosystemwithout mutating caller payloads@earendil-works/pi-aito0.77.0and add direct Opus 4.8 route testsRLMX_REPL_TIMEOUT_MSrobust against invalid/non-positive valuesPublic safety
Verification
npm run buildnpm run checknpm test→ 377 pass / 0 failDogfood
Artifacts:
/tmp/rlmx-dogfood-20260530T150150Z./dist/src/cli.js --schemaparsed successfully; required flags present (--schema,--context,--output,--stats,--thinking,--cache,--batch-api)RLMX_REPL_TIMEOUT_MS=not-a-number ./dist/src/cli.js ...returnedBAD_TIMEOUT_FALLBACK_OKopenrouter/deepseek-v4-flash; live smoke returnedOPENROUTER_COMPAT_OKwith non-zero stats:openrouter/deepseek/deepseek-v4-flash64611013ms$0.00015512anthropic / claude-opus-4-8Summary by CodeRabbit
New Features
Improvements
Chores
@earendil-works/pi-aidependency to v0.77.0.Tests