Skip to content

refactor(cli): extract model prompt helpers from onboard.js - #1515

Merged
cv merged 6 commits into
NVIDIA:mainfrom
cv:refactor/onboard-model-prompts-ts
Apr 6, 2026
Merged

refactor(cli): extract model prompt helpers from onboard.js#1515
cv merged 6 commits into
NVIDIA:mainfrom
cv:refactor/onboard-model-prompts-ts

Conversation

@cv

@cv cv commented Apr 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • move cloud/remote/manual model prompt helpers into src/lib/model-prompts.ts
  • keep onboard.js focused on inference-selection orchestration
  • add focused tests for back-navigation, default selection, missing-key handling, and validator retry behavior

Notes

  • merged main into this branch in acafdcc, so the PR now contains only the model-prompts extraction
  • explicitly handle missing NVIDIA_API_KEY before validating custom NVIDIA Endpoints models
  • handle the BACK_TO_SELECTION sentinel in both Ollama setup paths before attempting model preparation

Testing

  • npm run build:cli
  • npx vitest run --project cli src/lib/model-prompts.test.ts test/onboard-selection.test.js test/onboard.test.js test/credential-exposure.test.js
  • npm test

Summary by CodeRabbit

  • Bug Fixes

    • Fixed back navigation during local model entry—users can return to provider selection cleanly and see a confirmation log.
  • Refactor

    • Consolidated model selection and prompt flows into a shared module for more consistent behavior across cloud, remote, and manual model choices.
  • Tests

    • Added comprehensive tests covering cloud, remote, manual entry, navigation, validation, and retry scenarios.

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3cb2074e-5c4b-43c3-91ac-d6cf331e2ec0

📥 Commits

Reviewing files that changed from the base of the PR and between acafdcc and 07f1f70.

📒 Files selected for processing (2)
  • src/lib/model-prompts.test.ts
  • src/lib/model-prompts.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/model-prompts.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/model-prompts.ts

📝 Walkthrough

Walkthrough

Extracts model-selection prompt logic into a new module (src/lib/model-prompts.ts), removes hardcoded curated model lists from bin/lib/onboard.js, rebinds prompt helpers from the new module, updates Ollama flow to respect a back-to-selection sentinel, and adds unit and integration tests for prompt flows and back-navigation.

Changes

Cohort / File(s) Summary
Model Prompts Module
src/lib/model-prompts.ts, src/lib/model-prompts.test.ts
New module exporting prompt helpers and constants (BACK_TO_SELECTION, REMOTE_MODEL_OPTIONS, promptManualModelId, promptCloudModel, promptRemoteModel, promptInputModel) with injected dependencies, validation/defer rules, navigation handling, and a comprehensive unit test suite.
Onboard Refactoring
bin/lib/onboard.js
Removed local hardcoded CLOUD_MODEL_OPTIONS/REMOTE_MODEL_OPTIONS and in-file prompt implementations; imported prompt helpers from ../../dist/lib/model-prompts; retained provider-model validators; updated setupNim Ollama flow to log "Returning to provider selection." and continue selectionLoop when BACK_TO_SELECTION is returned.
Integration Test
test/onboard-selection.test.js
Added Vitest integration that spawns setupNim(null) to assert that selecting "back" during Ollama manual model entry returns to provider selection, logs the return message, and produces the expected prompt sequence and exit status.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nudged the prompts into a neat little den,
I hopped through back-buttons and validated when,
Curated lists tucked tidy and small,
Now flows return true when you say "back" to all.
Tests clap their paws — the onboarding's well then. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main refactoring: extracting model prompt helpers from onboard.js into a separate module, which is the core objective of this PR.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (5)
src/lib/model-prompts.ts (2)

131-137: Null credential passed to validator may cause unclear errors.

When getCredentialFn("NVIDIA_API_KEY") returns null, the fallback || "" passes an empty string to validateNvidiaEndpointModelFn. This will likely cause an authentication failure during validation, which might surface as a confusing error message rather than guiding the user to set their API key first.

Consider whether this edge case should be handled explicitly with a clearer prompt or error message.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/model-prompts.ts` around lines 131 - 137, The current call to
promptManualModelId passes deps.validateNvidiaEndpointModelFn(model,
deps.getCredentialFn("NVIDIA_API_KEY") || "") which masks a missing API key by
sending an empty string to validateNvidiaEndpointModelFn; update the flow to
explicitly check deps.getCredentialFn("NVIDIA_API_KEY") for null/undefined
before calling promptManualModelId (or call the validator) and, if missing,
return or surface a clear error/prompt telling the user to set the
NVIDIA_API_KEY; reference the functions promptManualModelId,
validateNvidiaEndpointModelFn and getCredentialFn("NVIDIA_API_KEY") when making
this change so the validator never receives a silent empty credential.

126-129: Consider handling NaN from parseInt explicitly.

If the user enters non-numeric text (e.g., "abc"), parseInt returns NaN, and NaN - 1 yields NaN. The condition index >= 0 && index < deps.cloudModelOptions.length evaluates to false for NaN, which correctly falls through to manual entry. However, this relies on implicit behavior that could confuse future maintainers.

The same pattern appears in promptRemoteModel at line 167.

♻️ Optional: Make NaN handling explicit
   const index = parseInt(choice || "1", 10) - 1;
-  if (index >= 0 && index < deps.cloudModelOptions.length) {
+  if (Number.isFinite(index) && index >= 0 && index < deps.cloudModelOptions.length) {
     return deps.cloudModelOptions[index].id;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/model-prompts.ts` around lines 126 - 129, The parseInt result for
choice in the index computation can be NaN; update the logic in the function
containing "const index = parseInt(choice || "1", 10) - 1;" (and the similar
code in promptRemoteModel) to explicitly detect NaN (e.g., using Number.isNaN)
before comparing ranges, so that non-numeric input is handled clearly and falls
through to manual entry; ensure you only use deps.cloudModelOptions[index].id
when the parsed index is a valid integer within [0,
deps.cloudModelOptions.length - 1].
src/lib/provider-models.ts (2)

37-52: parseModelIds can throw on malformed JSON; callers catch this but behavior is implicit.

JSON.parse will throw if body is not valid JSON. The calling functions (fetchNvidiaEndpointModels, etc.) wrap this in try-catch, so runtime errors are handled. However, the flow is implicit—a malformed response results in a generic error message rather than indicating a parse failure.

This is acceptable since the outer catch handles it, but documenting this behavior or returning an empty array on parse failure would make the intent clearer.

♻️ Optional: Handle parse errors gracefully within parseModelIds
 function parseModelIds(body: string, itemKeys: string[] = ["id"]): string[] {
+  let parsed: { data?: Array<Record<string, unknown> | null> };
+  try {
+    parsed = JSON.parse(body) as { data?: Array<Record<string, unknown> | null> };
+  } catch {
+    return [];
+  }
-  const parsed = JSON.parse(body) as { data?: Array<Record<string, unknown> | null> };
   if (!Array.isArray(parsed?.data)) return [];
   // ... rest unchanged
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/provider-models.ts` around lines 37 - 52, The parseModelIds function
can throw on invalid JSON; wrap the JSON.parse call inside parseModelIds
(function parseModelIds) with a try-catch and return an empty string[] on parse
failure so callers like fetchNvidiaEndpointModels don’t rely on implicit
external try-catch for JSON errors; ensure the function still validates
parsed?.data as an array and preserves the current mapping/filtering behavior
when parse succeeds.

147-150: Hardcoded Anthropic API version may become stale.

The anthropic-version: 2023-06-01 header is hardcoded and duplicated across multiple files (src/lib/provider-models.ts and bin/lib/onboard.js). While this is currently the latest version, making it configurable via ProviderModelOptions or extracting it to a constant would improve maintainability and reduce the need for coordinated updates across files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/provider-models.ts` around lines 147 - 150, The anthropic-version
header is hardcoded; extract it into a single configurable constant (e.g.,
ANTHROPIC_API_VERSION) or add it to ProviderModelOptions so callers can
override, then replace the literal "anthropic-version: 2023-06-01" in the header
array used with normalizeCredentialValue(apiKey) with a reference to that
constant/option; ensure all other places that set the same header (e.g.,
onboarding logic) read the same constant/option so the version is centralized
and not duplicated.
src/lib/validation-recovery.ts (1)

72-86: Consider caching classification results to avoid repeated iterations.

classifyValidationFailure is called multiple times on the same failures across .some(), .find(), and the fallback logic. For small failure arrays this is fine, but it could be optimized by pre-classifying once.

♻️ Optional: Pre-classify failures to avoid repeated work
 export function getProbeRecovery(
   probe: ProbeLike,
   options: ProbeRecoveryOptions = {},
 ): ProbeRecovery {
   const allowModelRetry = options.allowModelRetry === true;
   const failures = Array.isArray(probe?.failures) ? probe.failures : [];
   if (failures.length === 0) {
     return { kind: "unknown", retry: "selection" };
   }
+  const classified = failures.map((f) => ({ failure: f, classification: classifyValidationFailure(f) }));
-  if (failures.some((failure) => classifyValidationFailure(failure).kind === "credential")) {
+  if (classified.some((c) => c.classification.kind === "credential")) {
     return { kind: "credential", retry: "credential" };
   }
-  const transportFailure = failures.find(
-    (failure) => classifyValidationFailure(failure).kind === "transport",
-  );
+  const transportEntry = classified.find((c) => c.classification.kind === "transport");
+  const transportFailure = transportEntry?.failure;
   if (transportFailure) {
     return { kind: "transport", retry: "retry", failure: transportFailure };
   }
-  if (allowModelRetry && failures.some((failure) => classifyValidationFailure(failure).kind === "model")) {
+  if (allowModelRetry && classified.some((c) => c.classification.kind === "model")) {
     return { kind: "model", retry: "model" };
   }
-  if (failures.some((failure) => classifyValidationFailure(failure).kind === "endpoint")) {
+  if (classified.some((c) => c.classification.kind === "endpoint")) {
     return { kind: "endpoint", retry: "selection" };
   }
-  const fallback = classifyValidationFailure(failures[0]);
+  const fallback = classified[0].classification;
   if (!allowModelRetry && fallback.kind === "model") {
     return { kind: "unknown", retry: "selection" };
   }
   return fallback;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/validation-recovery.ts` around lines 72 - 86, Pre-classify the
failures once instead of calling classifyValidationFailure repeatedly: create an
array (e.g., const classified = failures.map(f => ({ failure: f, kind:
classifyValidationFailure(f).kind })) or a Map) and then use that classified
collection for the subsequent checks (replace calls in the .some() and .find()
checks and the allowModelRetry branch), updating transportFailure to be taken
from classified (e.g., classified.find(c => c.kind === "transport")?.failure) so
all branches reference the pre-computed kinds (referencing
classifyValidationFailure, failures, allowModelRetry, and transportFailure).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/onboard.js`:
- Around line 1367-1368: The Ollama branches call promptOllamaModel() and pass
its result straight into prepareOllamaModel(), but
promptManualModelId()/promptOllamaModel() can return the BACK_TO_SELECTION
sentinel (e.g. "__NEMOCLAW_BACK_TO_SELECTION__"); update the Ollama flow so
after calling promptOllamaModel() you check if the return equals the
BACK_TO_SELECTION sentinel and if so return/propagate that sentinel (or re-enter
provider selection) instead of calling prepareOllamaModel(); adjust the branches
that currently call prepareOllamaModel() directly (the Ollama branch that
invokes promptOllamaModel and then prepareOllamaModel) to handle this early
return.

In `@src/lib/http-probe.ts`:
- Around line 103-114: The Node process timeout passed to spawnSyncImpl
(currently 30_000) can fire before curl's own --max-time (60s) used by
getCurlTimingArgs(), so update the call that invokes spawnSyncImpl with the curl
args (the block constructing [...args, "-o", bodyFile, "-w", "%{http_code}",
String(url || "")]) to compute a timeout that is at least as long as any
--max-time provided by the args (convert seconds to ms) plus a small buffer
(e.g., 1-2s); implement this by scanning the args array for a "--max-time"
value, using Math.max(currentTimeout, parsedMaxTime*1000 + buffer) when building
the options object (cwd/encoding/env remain unchanged) so Node won't kill the
process before curl's timeout and error 28 is preserved.

---

Nitpick comments:
In `@src/lib/model-prompts.ts`:
- Around line 131-137: The current call to promptManualModelId passes
deps.validateNvidiaEndpointModelFn(model, deps.getCredentialFn("NVIDIA_API_KEY")
|| "") which masks a missing API key by sending an empty string to
validateNvidiaEndpointModelFn; update the flow to explicitly check
deps.getCredentialFn("NVIDIA_API_KEY") for null/undefined before calling
promptManualModelId (or call the validator) and, if missing, return or surface a
clear error/prompt telling the user to set the NVIDIA_API_KEY; reference the
functions promptManualModelId, validateNvidiaEndpointModelFn and
getCredentialFn("NVIDIA_API_KEY") when making this change so the validator never
receives a silent empty credential.
- Around line 126-129: The parseInt result for choice in the index computation
can be NaN; update the logic in the function containing "const index =
parseInt(choice || "1", 10) - 1;" (and the similar code in promptRemoteModel) to
explicitly detect NaN (e.g., using Number.isNaN) before comparing ranges, so
that non-numeric input is handled clearly and falls through to manual entry;
ensure you only use deps.cloudModelOptions[index].id when the parsed index is a
valid integer within [0, deps.cloudModelOptions.length - 1].

In `@src/lib/provider-models.ts`:
- Around line 37-52: The parseModelIds function can throw on invalid JSON; wrap
the JSON.parse call inside parseModelIds (function parseModelIds) with a
try-catch and return an empty string[] on parse failure so callers like
fetchNvidiaEndpointModels don’t rely on implicit external try-catch for JSON
errors; ensure the function still validates parsed?.data as an array and
preserves the current mapping/filtering behavior when parse succeeds.
- Around line 147-150: The anthropic-version header is hardcoded; extract it
into a single configurable constant (e.g., ANTHROPIC_API_VERSION) or add it to
ProviderModelOptions so callers can override, then replace the literal
"anthropic-version: 2023-06-01" in the header array used with
normalizeCredentialValue(apiKey) with a reference to that constant/option;
ensure all other places that set the same header (e.g., onboarding logic) read
the same constant/option so the version is centralized and not duplicated.

In `@src/lib/validation-recovery.ts`:
- Around line 72-86: Pre-classify the failures once instead of calling
classifyValidationFailure repeatedly: create an array (e.g., const classified =
failures.map(f => ({ failure: f, kind: classifyValidationFailure(f).kind })) or
a Map) and then use that classified collection for the subsequent checks
(replace calls in the .some() and .find() checks and the allowModelRetry
branch), updating transportFailure to be taken from classified (e.g.,
classified.find(c => c.kind === "transport")?.failure) so all branches reference
the pre-computed kinds (referencing classifyValidationFailure, failures,
allowModelRetry, and transportFailure).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c1cca71-f5b5-4377-8f2a-b24c7f5cd5d3

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd3285 and 94b9430.

📒 Files selected for processing (11)
  • bin/lib/onboard.js
  • src/lib/http-probe.test.ts
  • src/lib/http-probe.ts
  • src/lib/model-prompts.test.ts
  • src/lib/model-prompts.ts
  • src/lib/provider-models.test.ts
  • src/lib/provider-models.ts
  • src/lib/validation-recovery.test.ts
  • src/lib/validation-recovery.ts
  • test/credential-exposure.test.js
  • test/onboard.test.js

Comment thread bin/lib/onboard.js
Comment thread src/lib/http-probe.ts

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
bin/lib/onboard.js (1)

1365-1366: Import BACK_TO_SELECTION from model-prompts as well.

Line 99 still hardcodes the sentinel while this import already pulls the rest of the prompt contract from modelPrompts. Keeping the sentinel duplicated in two modules is easy to desync in follow-up extractions and would silently break the new back-navigation checks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 1365 - 1366, The code imports several
symbols from modelPrompts but omits the BACK_TO_SELECTION sentinel; update the
destructuring import (where promptManualModelId, promptCloudModel,
promptRemoteModel, promptInputModel are imported from modelPrompts) to also
include BACK_TO_SELECTION, then replace any hardcoded sentinel usage (the one
referenced as the duplicated constant) with the imported BACK_TO_SELECTION to
keep the prompt contract centralized and avoid desync between modules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/model-prompts.ts`:
- Around line 96-101: The manual model-id prompt currently treats all
validator() failures as invalid IDs and loops, which swallows transient
auth/transport errors when promptCloudModel() passes a live /models validator;
change promptManualModelId (and the similar block at the other occurrence) to
distinguish validation failures from transient errors by having the validator
return or throw a distinct transient/retry signal (e.g., a {retry: true} result
or a specific Error subtype) instead of only ok/message, and when that transient
signal is seen call deps.errorLine with the error message and propagate a retry
indication back to the caller (do not continue the input loop); update
promptCloudModel to use that contract for the validator so auth/transport
failures bubble up to the caller’s recovery flow rather than being treated as
bad model ids.

---

Nitpick comments:
In `@bin/lib/onboard.js`:
- Around line 1365-1366: The code imports several symbols from modelPrompts but
omits the BACK_TO_SELECTION sentinel; update the destructuring import (where
promptManualModelId, promptCloudModel, promptRemoteModel, promptInputModel are
imported from modelPrompts) to also include BACK_TO_SELECTION, then replace any
hardcoded sentinel usage (the one referenced as the duplicated constant) with
the imported BACK_TO_SELECTION to keep the prompt contract centralized and avoid
desync between modules.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72384c62-751d-478f-97f3-92b241c48871

📥 Commits

Reviewing files that changed from the base of the PR and between 94b9430 and acafdcc.

📒 Files selected for processing (4)
  • bin/lib/onboard.js
  • src/lib/model-prompts.test.ts
  • src/lib/model-prompts.ts
  • test/onboard-selection.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/model-prompts.test.ts

Comment thread src/lib/model-prompts.ts
@cv
cv merged commit 90b9175 into NVIDIA:main Apr 6, 2026
8 checks passed
@cv
cv deleted the refactor/onboard-model-prompts-ts branch April 6, 2026 01:03
tranzmatt pushed a commit to tranzmatt/NemoClaw that referenced this pull request Apr 6, 2026
)

## Summary
- move cloud/remote/manual model prompt helpers into
`src/lib/model-prompts.ts`
- keep `onboard.js` focused on inference-selection orchestration
- add focused tests for back-navigation, default selection, missing-key
handling, and validator retry behavior

## Notes
- merged `main` into this branch in `acafdcc`, so the PR now contains
only the model-prompts extraction
- explicitly handle missing `NVIDIA_API_KEY` before validating custom
NVIDIA Endpoints models
- handle the `BACK_TO_SELECTION` sentinel in both Ollama setup paths
before attempting model preparation

## Testing
- `npm run build:cli`
- `npx vitest run --project cli src/lib/model-prompts.test.ts
test/onboard-selection.test.js test/onboard.test.js
test/credential-exposure.test.js`
- `npm test`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed back navigation during local model entry—users can return to
provider selection cleanly and see a confirmation log.

* **Refactor**
* Consolidated model selection and prompt flows into a shared module for
more consistent behavior across cloud, remote, and manual model choices.

* **Tests**
* Added comprehensive tests covering cloud, remote, manual entry,
navigation, validation, and retry scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
gemini2026 pushed a commit to gemini2026/NemoClaw that referenced this pull request Apr 14, 2026
)

## Summary
- move cloud/remote/manual model prompt helpers into
`src/lib/model-prompts.ts`
- keep `onboard.js` focused on inference-selection orchestration
- add focused tests for back-navigation, default selection, missing-key
handling, and validator retry behavior

## Notes
- merged `main` into this branch in `acafdcc`, so the PR now contains
only the model-prompts extraction
- explicitly handle missing `NVIDIA_API_KEY` before validating custom
NVIDIA Endpoints models
- handle the `BACK_TO_SELECTION` sentinel in both Ollama setup paths
before attempting model preparation

## Testing
- `npm run build:cli`
- `npx vitest run --project cli src/lib/model-prompts.test.ts
test/onboard-selection.test.js test/onboard.test.js
test/credential-exposure.test.js`
- `npm test`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Fixed back navigation during local model entry—users can return to
provider selection cleanly and see a confirmation log.

* **Refactor**
* Consolidated model selection and prompt flows into a shared module for
more consistent behavior across cloud, remote, and manual model choices.

* **Tests**
* Added comprehensive tests covering cloud, remote, manual entry,
navigation, validation, and retry scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@wscurran wscurran added the refactor PR restructures code without intended behavior change label Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor PR restructures code without intended behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants