Skip to content

feat(providers): integrate DeepSeek, NVIDIA NIM, LM Studio, llama.cpp - #997

Merged
murdore merged 1 commit into
releasefrom
feat/provider-intgeration
Apr 30, 2026
Merged

murdore merged 1 commit into
releasefrom
feat/provider-intgeration

Conversation

@murdore

@murdore murdore commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds 4 OpenAI-compatible AI providers via Vercel AI SDK's createOpenAI().chat(): DeepSeek, NVIDIA NIM, LM Studio, llama.cpp
  • Includes pricing entries with _default sentinel as provider-level fallback, vision capability flags, model enums, CLI choices, and full E2E test coverage via test/continuous-test-suite-new-providers.ts
  • 10+ pre-existing test infrastructure bugs uncovered and fixed during validation (the biggest: maxTokens fallback in 12 test files defaulted unknown providers to their full context window → 0 input budget for memory/context/mcp tests)
  • Fixes root-cause getOutputReserve clamp at 80% of context window so callers passing oversized maxTokens get headroom instead of "Budget: 0 tokens"

What's new

Providers

  • src/lib/providers/deepseek.ts — cloud, requires DEEPSEEK_API_KEY
  • src/lib/providers/nvidiaNim.ts — cloud, requires NVIDIA_NIM_API_KEY, supports NIM-specific extras (top_k, min_p, repetition_penalty, reasoning_budget) with retry-on-400 stripping
  • src/lib/providers/lmStudio.ts — local, auto-discovers loaded model via /v1/models
  • src/lib/providers/llamaCpp.ts — local, auto-discovers + 3x retry on /health

All 4 extend BaseProvider, emit neurolink.provider.stream OTEL spans via withClientSpan, and ship with friendly formatProviderError (auth, rate limit, balance/quota, model-not-found).

Credential pass-through verified end-to-end

Per-call options.credentials > NeuroLink instance credentials > env vars > documented defaults. Optional LM_STUDIO_API_KEY / LLAMACPP_API_KEY for users running local servers behind an auth-proxying reverse-proxy.

Pricing (src/lib/utils/pricing.ts)

  • 4 provider entries with symbolic local rates ($1/M tokens) so cost attribution survives 6-decimal rounding
  • _default sentinel now treated as provider-level fallback — filtered from prefix matches, used as last resort

Context budget root-cause fix (src/lib/constants/contextWindows.ts)

getOutputReserve now clamps to 80% of context window. Previously, callers passing maxTokens === contextWindow (e.g. tests with a generic per-provider cap) zeroed out the input budget — every request failed with "Budget: 0 tokens" before being sent.

BaseProvider.modelName no longer readonly

Auto-discovery providers (lm-studio, llamacpp) need to update modelName after /v1/models so handlers (TelemetryHandler, MessageBuilder) cache the resolved name.

Test infrastructure fixes (15 files)

  • Twelve test files: PROVIDER_MAX_TOKENS[provider] || 8192 → || 1024 + new providers added explicitly
  • continuous-test-suite-providers.ts: Gemini 3 - DisableTools made provider-agnostic; Observability Spans now SKIPs cleanly for Pipeline-A providers (AI SDK + Langfuse OTEL — they intentionally don't emit Pipeline-B spans)
  • continuous-test-suite-context.ts: Context Compaction Vertex Flash/Pro use TEST_PROVIDER with vertex fallback
  • continuous-test-suite-evaluation.ts: dimension-specific framing in scoreAnswerOnDimension so RAGAS Context Precision evaluates the context (not the answer)
  • continuous-test-suite-client.ts: pass timeout: TEST_CONFIG.timeout to createServer
  • continuous-test-suite-observability.ts: read TEST_TIMEOUT_MS from env

Tooling

  • test/continuous-test-suite-new-providers.ts (10 sections × 4 providers, 868 LOC)
  • test/run-provider-matrix.sh (bash 3.2-compat matrix runner)
  • New test:dynamic, test:proxy, test:bugfixes, test:new-providers scripts
  • Added js-yaml + @types/js-yaml devDeps (proxy Config Loading test fixture)

Documentation

docs/provider-integration/ — 14 markdown files covering architecture, per-provider integration notes, testing methodology, the failure-investigation trail, and the PR analysis.

Final test matrix (36 cells × 4 providers, 9 suites each)

Provider Cells PASS Sub-tests PASS Pass-rate
DeepSeek 9/9 ✅ 101/101 100%
NVIDIA NIM 9/9 ✅ 90/90 100%
LM Studio 8/9 101/103 98.1%
llama.cpp 6/9 89/93 95.7%

Overall: 380/386 sub-tests PASS (98.4%). All 6 remaining sub-test failures are documented as local-3B-model judge-quality issues (e.g. small Llama 3.2 3B can't reliably score "context precision" in the focused-vs-bloated RAGAS scenario) or one pre-existing SDK observability item (All Spans Have Status — neurolink.tool.execute spans don't set OTEL status, affects all providers, not new). Zero failures in the new provider code.

Test plan

  • pnpm run check — 0 errors / 0 warnings (3632 files)
  • pnpm run lint — 0 errors / 18 warnings (all pre-existing in unrelated files)
  • pnpm run build — clean
  • All 4 providers smoke-tested end-to-end via env, instance-level, and per-call credential paths
  • 36-cell matrix run with all fixes verified (results in docs/provider-integration/10-test-results-final.md)
  • All 4 explicit sub-test failures from earlier iterations investigated and fixed where they were code bugs (see docs/provider-integration/11-test-failure-investigation.md)
  • Code review (self-review since reviewer agent rate-limited; see docs/provider-integration/13-code-review.md)

Summary by CodeRabbit

  • New Features

    • Support for DeepSeek, NVIDIA NIM, LM Studio (local) and llama.cpp (local); CLI provider aliases and model auto-discovery.
  • Documentation

    • Extensive provider-integration guides and updated .env.example with optional runtime and test-only credential/base-URL entries.
  • Tests

    • New provider-focused continuous test suites, a provider×suite matrix runner, and per-provider token caps; lowered fallback maxTokens for unmapped providers.
  • Behavior Changes

    • Updated pricing, context-window and vision capability mappings; improved timeout/retry and user-facing error messages.
  • Chores

    • Added test scripts and YAML tooling to dev dependencies.

@vercel

vercel Bot commented Apr 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
neurolink Ready Ready Preview, Comment Apr 30, 2026 2:07pm

@coderabbitai

coderabbitai Bot commented Apr 28, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21358368-c6df-41be-a1df-4a05a60bd533

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds four OpenAI‑compatible providers (DeepSeek, NVIDIA NIM, LM Studio, llama.cpp): provider classes, registry registrations, enums/types, credential shapes, provider-config helpers, pricing/context/vision updates, CLI/model-choice changes, .env.example additions, proxy helper export, extensive provider-integration docs, and broad test-suite additions including a new provider test runner.

Changes

Cohort / File(s) Summary
Documentation
docs/provider-integration/*
New, extensive provider-integration docs (architecture, shared changes, per-provider specs, testing, implementation order, matrices, test results, investigations, PR analysis, code-review guidance).
Environment & Scripts
.env.example, package.json
Adds env vars for DeepSeek/NVIDIA NIM/LM Studio/llamacpp (API keys, models, base URLs, commented TEST_* overrides); adds new test npm scripts and js-yaml dev deps.
Provider Implementations
src/lib/providers/deepseek.ts, src/lib/providers/nvidiaNim.ts, src/lib/providers/lmStudio.ts, src/lib/providers/llamaCpp.ts
Adds four provider classes with streaming, model discovery/auto-discover, tool hooks, telemetry/span instrumentation, NIM retry-on-400 logic, analytics, error normalization, and configuration validation.
Registry & Barrel Exports
src/lib/factories/providerRegistry.ts, src/lib/providers/index.ts
Registers new providers via lazy dynamic imports, supplies env-driven default models, registers aliases, and updates barrel exports.
Core Types, Enums & BaseProvider
src/lib/constants/enums.ts, src/lib/types/providers.ts, src/lib/core/baseProvider.ts
Adds provider/model enums and credential shapes; makes modelName and model-dependent handlers mutable and introduces refreshHandlersForModel() to rebuild handlers and update spans after auto-discovery.
CLI & UX
src/cli/factories/commandFactory.ts, src/lib/utils/modelChoices.ts
Expands CLI provider identifiers/completions and model-choice lists; adds auto-discover sentinel for local providers and adjusts UI labels.
Provider Config & Helpers
src/lib/utils/providerConfig.ts, src/lib/utils/pricing.ts
Adds provider-config factory helpers (DeepSeek, NVIDIA NIM, LM Studio, llama.cpp), marks local base-URL configs optional, adds pricing entries and _default zero-rate for local providers, and refines alias normalization and rate lookup logic.
Vision & Adapters
src/lib/adapters/providerImageAdapter.ts, src/lib/constants/contextWindows.ts
Adds alias normalization, updates vision allowlists (empty-allowlist semantics), filters out empty allowlists, and adds static context-window mappings with alias-aware lookup.
Proxy Helpers
src/lib/proxy/proxyFetch.ts
Exports maskProxyUrl(...) for provider-side fetch logging/sanitization.
Tests — Suites & Runner
test/continuous-test-suite-*.ts, test/continuous-test-suite-new-providers.ts, test/run-provider-matrix.sh
Adds a new provider-focused test suite and matrix runner; extends many test suites to include new providers, normalizes credential matching, adds PROVIDER_MAX_TOKENS entries, and changes unmapped-provider fallback maxTokens from 8192 → 1024.
Utilities & Misc
src/lib/utils/*, test/*
Various normalization, telemetry/span updates, model-choice and pricing normalization, provider-config helpers, and widespread test-harness adjustments.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Client
    participant Registry as ProviderRegistry
    participant Provider as Provider (DeepSeek/NIM/LM/llama)
    participant SDK as OpenAI-compatible SDK
    participant Remote as Remote Server

    Client->>Registry: request provider(key, credentials)
    Registry->>Registry: dynamic import(...) (lazy)
    Registry->>Provider: new Provider(model?, sdk?, _, credentials?)
    Registry-->>Client: provider instance

    Client->>Provider: executeStream(messages, options)
    Provider->>Provider: maybe refreshHandlersForModel() (auto-discover)
    Provider->>SDK: streamText({ model, messages, providerOptions })
    SDK->>Remote: POST /v1/chat/completions (SSE)
    Remote-->>SDK: SSE chunks / tool events
    SDK-->>Provider: stream events
    Provider->>Provider: onStepFinish -> run tools, emit tool-end, persist results
    Provider->>Provider: collect analytics, transform stream
    Provider-->>Client: { text stream, analytics promise, metadata }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested Labels

released

Suggested Reviewers

  • charan-vadd
  • moyyyy3333

Poem

🐰 Hopped through docs, configs, and streams so bright,
four providers tucked in my burrow tonight.
Keys in the leaves, local servers hum low,
tests line up tidy, telemetry aglow.
A carrot for CI — build, test, then go! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.55% 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 accurately describes the main change: adding four new provider integrations (DeepSeek, NVIDIA NIM, LM Studio, llama.cpp) to the codebase.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/provider-intgeration

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.

@github-actions

github-actions Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: 08dbe1934a46755af9b879d907240da87d1bb85b
  • Message: feat(providers): integrate DeepSeek, NVIDIA NIM, LM Studio, llama.cpp
  • Author: Sachin Sharma

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

github-actions Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

Documentation Validation Results

🚀 Documentation validation passed!

Check Status Result
Frontmatter Validation ✅ Passed
TypeScript Check ✅ Passed
Build ✅ Passed
Link Validation ✅ Passed

📦 Build artifact uploaded successfully. Ready for deployment preview.

Commit: e027ce309874e172f8ee0bca2df41178570bf4f2 | Workflow: View logs

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
test/continuous-test-suite-mcp.ts (1)

152-154: ⚠️ Potential issue | 🟡 Minor

Align fallback token budget with the suite-wide default.

Line 154 still falls back to 4096, while other continuous suites use 1024. This makes unmapped-provider behavior inconsistent and can increase flake/cost.

🔧 Proposed fix
-TEST_CONFIG.maxTokens = PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] || 4096;
+TEST_CONFIG.maxTokens = PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] || 1024;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-mcp.ts` around lines 152 - 154, The fallback token
budget currently uses 4096 when a provider key is missing; update the default to
match other suites by changing the fallback in the assignment to
TEST_CONFIG.maxTokens (which uses PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] ||
4096) to 1024 so unmapped providers use the suite-wide default; verify this code
path where TEST_CONFIG.model is set via resolveTestModel(TEST_CONFIG.provider)
remains unchanged.
🟡 Minor comments (11)
test/continuous-test-suite-credentials.ts-539-542 (1)

539-542: ⚠️ Potential issue | 🟡 Minor

Fix provider-key normalization mismatch in credential/provider alignment check.

Line 540 and Line 541 use de-hyphenated keys (nvidianim, lmstudio), but matching logic compares raw strings. That means nvidia-nim and lm-studio won’t match as intended.

🔧 Proposed fix
-      "nvidianim", // normalized: nvidiaNim -> lowercase
-      "lmstudio", // normalized: lmStudio -> lowercase
+      "nvidia-nim",
+      "lm-studio",

Also applies to: 548-559

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

In `@test/continuous-test-suite-credentials.ts` around lines 539 - 542, The
credential/provider alignment check is comparing raw provider keys to normalized
entries like "nvidianim" and "lmstudio", causing mismatches for keys such as
"nvidia-nim" and "lm-studio"; update the comparison in the provider alignment
routine (the credential/provider alignment check function) to compare normalized
forms by applying the same normalization to both sides (e.g., s =>
s.toLowerCase().replace(/-/g, '')) so "nvidia-nim" equals "nvidianim" and
"lm-studio" equals "lmstudio" wherever the array entries "nvidianim" and
"lmstudio" are referenced (also apply same fix to the other checks in the same
block that cover the entries at the later range).
docs/provider-integration/12-pr-analysis.md-12-12 (1)

12-12: ⚠️ Potential issue | 🟡 Minor

Use proper noun casing: “Markdown”.

Line 12 should use “Markdown” (proper noun), not “markdown”.

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

In `@docs/provider-integration/12-pr-analysis.md` at line 12, Update the text
fragment "- **13 new markdown docs** (~150KB)" to use proper noun casing by
replacing "markdown" with "Markdown" so it reads "- **13 new Markdown docs**
(~150KB)"; locate the exact string in the document (the line containing "**13
new markdown docs**") and make the single-word casing change.
docs/provider-integration/12-pr-analysis.md-229-230 (1)

229-230: ⚠️ Potential issue | 🟡 Minor

Deferred-item note is stale vs implemented code.

Lines 229–230 say the getOutputReserve clamping fix is “NOT in this PR,” but this PR includes that change in src/lib/constants/contextWindows.ts. Please update this section to avoid contradictory release documentation.

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

In `@docs/provider-integration/12-pr-analysis.md` around lines 229 - 230, Update
the release note that incorrectly states getOutputReserve clamping is "NOT in
this PR"—change the wording to reflect that the clamping fix was implemented in
this PR by referencing the change in src/lib/constants/contextWindows.ts (the
getOutputReserve behavior now clamps maxTokens to contextWindow * 0.8) so the
documentation no longer contradicts the code.
src/cli/factories/commandFactory.ts-3931-3931 (1)

3931-3931: ⚠️ Potential issue | 🟡 Minor

Autocomplete provider list is out of sync with accepted --provider choices.

Line 3931 omits several valid choices that are accepted in commonOptions.provider.choices (for example openrouter, openai-compatible, google-ai-studio, sagemaker, and aliases like ds/lmstudio/llama.cpp). This causes misleading tab completion.

🔧 Suggested fix
-                    COMPREPLY=( $(compgen -W "auto openai bedrock vertex googleVertex anthropic azure google-ai huggingface ollama mistral litellm deepseek nvidia-nim lm-studio llamacpp" -- ${cur}) )
+                    COMPREPLY=( $(compgen -W "auto openai openai-compatible openrouter or bedrock vertex googleVertex anthropic anthropic-subscription azure google-ai google-ai-studio huggingface ollama mistral litellm sagemaker deepseek ds nvidia-nim nim lm-studio lmstudio llamacpp llama.cpp" -- ${cur}) )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/factories/commandFactory.ts` at line 3931, The bash completion list
in the COMPREPLY assignment (the string containing compgen -W "...") is missing
several valid entries from commonOptions.provider.choices; update that quoted
word list to exactly match all accepted provider names and aliases (e.g., add
openrouter, openai-compatible, google-ai-studio, sagemaker and aliases like ds,
lmstudio, llama.cpp/llamacpp) so the COMPREPLY/compgen -W string is kept in sync
with commonOptions.provider.choices; ensure any future changes are mirrored here
or consider deriving the list from the same provider choices source to avoid
drift.
docs/provider-integration/README.md-3-3 (1)

3-3: ⚠️ Potential issue | 🟡 Minor

Remove machine-specific absolute paths from docs.

Hardcoded local paths (/Users/...) make the guide non-portable and leak personal workstation structure. Use repo-relative references or generic placeholders.

Also applies to: 27-27

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

In `@docs/provider-integration/README.md` at line 3, Replace the hardcoded
absolute path in the README line that reads "Implementation documentation for
porting four providers from
`/Users/sachinsharma/Developer/temp/ai-coder/free-claude-code` into Neurolink"
with a repo-relative path or a generic placeholder (e.g.,
"./examples/free-claude-code" or "<path/to/source-repo>") so the docs are
portable and do not leak local filesystem details; update any identical
occurrences elsewhere in the file.
docs/provider-integration/README.md-35-45 (1)

35-45: ⚠️ Potential issue | 🟡 Minor

Status section is stale versus current PR state.

The table says all items are “Spec only,” but this PR already includes provider implementations and passing test coverage. Please update this section to prevent reader confusion.

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

In `@docs/provider-integration/README.md` around lines 35 - 45, Update the
"Status" section table to reflect the current PR state: change the rows for
"DeepSeek implementation", "NVIDIA NIM implementation", "LM Studio
implementation", "llama.cpp implementation", "Shared changes (types/CLI/etc)"
and "Tests" from "⏳ Spec only" to statuses indicating they are implemented
(e.g., "✅ Implemented" or "✅ Implemented, tests passing") and mention that
provider implementations and test coverage are included in this PR so readers
are not misled by stale information.
docs/provider-integration/00-architecture.md-95-106 (1)

95-106: ⚠️ Potential issue | 🟡 Minor

Fix markdownlint MD031 around fenced block.

The fenced code block after the bullet list needs surrounding blank lines to satisfy markdownlint (blanks-around-fences).

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

In `@docs/provider-integration/00-architecture.md` around lines 95 - 106, The
fenced TypeScript block documenting ProviderConfigOptions violates markdownlint
MD031 because it is not separated from the preceding list item by blank lines;
update the markdown around the fenced block (the snippet following the bullet
mentioning createMistralConfig(), createOpenAIConfig(), etc.) by inserting an
empty line before the opening ```ts and an empty line after the closing ``` so
the fenced block is surrounded by blank lines and MD031 is resolved.
docs/provider-integration/10-test-results-final.md-77-83 (1)

77-83: ⚠️ Potential issue | 🟡 Minor

Clarify pass-rate denominator in the provider table.

The Pass-rate values appear to use a different denominator than Total sub-tests for some providers. Please add an explicit SKIP column and define pass-rate formula to prevent misinterpretation.

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

In `@docs/provider-integration/10-test-results-final.md` around lines 77 - 83, The
table's pass-rate uses a different denominator than "Total sub-tests"; add an
explicit "SKIP" column in the header and for each provider row (e.g., DeepSeek,
NVIDIA NIM, LM Studio, llama.cpp) populate SKIP counts, then recalc Pass-rate
using a clear formula in a footnote or header: "Pass-rate = PASS / (Total
sub-tests - SKIP) × 100%". Also update the table caption or a short note to
define the formula so readers won't misinterpret the denominator.
docs/provider-integration/06-testing.md-36-43 (1)

36-43: ⚠️ Potential issue | 🟡 Minor

Provider-loop behavior description is inaccurate.

This section says the loop calls validateConfiguration() first, but the current provider suite loop executes generate/stream and derives SKIP from expected error handling. Please align the narrative with actual control flow.

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

In `@docs/provider-integration/06-testing.md` around lines 36 - 43, The
documentation text is inaccurate about the provider test loop control flow:
update the narrative to reflect that the actual loop in the test suite iterates
ALL_PROVIDERS and calls generate("Hi") / stream("Hi") first, and determines SKIP
from the observed error handling rather than by calling validateConfiguration()
up-front; mention the actual behavior of validateConfiguration() (used for
probes by local providers like LM Studio/llama.cpp) and clarify that SKIP is
derived from generated errors rather than a prior boolean check from
validateConfiguration().
docs/provider-integration/06-testing.md-58-65 (1)

58-65: ⚠️ Potential issue | 🟡 Minor

Use the same response field naming as the active suites.

These snippets assert result?.text, while the continuous suites in this PR consistently consume result.content. Aligning examples will reduce copy/paste confusion.

Also applies to: 76-83, 99-106, 121-128

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

In `@docs/provider-integration/06-testing.md` around lines 58 - 65, The sample
uses result?.text but the project’s active suites expect result.content; update
the nl.generate usage and assertions to consume result.content instead of
result.text (e.g., change the assertion to assert(typeof result?.content ===
"string", "should return content")) for the shown block and the other
occurrences noted (lines referenced in the comment) so examples match the
continuous suites' response field naming; keep the same call to nl.generate and
its parameters (provider "deepseek", credentials, maxTokens).
test/continuous-test-suite-new-providers.ts-377-388 (1)

377-388: ⚠️ Potential issue | 🟡 Minor

Don't return out of the whole tools section when zod is missing.

The early return skips B4 tools.disable as well, even though that case does not depend on zod. This leaves a silent coverage gap and undercounts skipped tests for section 2.

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

In `@test/continuous-test-suite-new-providers.ts` around lines 377 - 388, The
catch block currently returns early when zod is missing, skipping later tests
like "B4 tools.disable"; instead remove the early "return" and leave zodMod set
to null after logging and marking B1 tests as SKIP. Ensure the B1 test logic
that uses zodMod (references: zodMod variable, PROVIDERS loop, logTest, record)
is guarded to skip when zodMod is null, so execution continues to run subsequent
sections (including B4 tools.disable) even if zod is not installed.
🧹 Nitpick comments (7)
test/continuous-test-suite-tts.ts (1)

1874-1874: Prefer nullish fallback and explicit unknown-provider signal at Line 1874.

This is a small hardening improvement: || can hide unexpected provider keys and any intentional 0 values.

Optional tweak
-if (!TEST_CONFIG.maxTokens) {
-  TEST_CONFIG.maxTokens = PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] || 1024;
-}
+if (!TEST_CONFIG.maxTokens) {
+  const providerCap = PROVIDER_MAX_TOKENS[TEST_CONFIG.provider];
+  TEST_CONFIG.maxTokens = providerCap ?? 1024;
+  if (providerCap === undefined) {
+    log(
+      `Unknown provider "${TEST_CONFIG.provider}", defaulting maxTokens to 1024`,
+      "yellow",
+    );
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-tts.ts` at line 1874, Replace the loose fallback
using || when setting TEST_CONFIG.maxTokens so that zero values are preserved
and unexpected/missing provider keys are detected: read
PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] into a variable, use the nullish
coalescing operator (??) to fall back to 1024 only when the lookup is null or
undefined, and add an explicit branch that signals an unknown provider (e.g.,
warn/log or set a distinct sentinel) when the lookup is undefined; update the
assignment site (TEST_CONFIG.maxTokens and the PROVIDER_MAX_TOKENS lookup)
accordingly.
test/continuous-test-suite-session-memory-bugs.ts (1)

1013-1013: Optional refinement at Line 1013: prefer ?? over || for fallback.

Small robustness/readability improvement, same rationale as other suites.

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

In `@test/continuous-test-suite-session-memory-bugs.ts` at line 1013, Replace the
fallback used when setting TEST_CONFIG.maxTokens so it uses the nullish
coalescing operator instead of logical OR; specifically update the assignment
that references PROVIDER_MAX_TOKENS and TEST_CONFIG.provider (the expression
assigning TEST_CONFIG.maxTokens) to use ?? for fallback to 1024 so
undefined/null provider entries don't incorrectly fall back when falsy values
are valid.
test/continuous-test-suite-ppt.ts (1)

1616-1616: Optional fallback hardening at Line 1616.

Using ?? instead of || avoids masking falsy numeric values and makes unknown-provider fallback behavior clearer.

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

In `@test/continuous-test-suite-ppt.ts` at line 1616, The assignment to
TEST_CONFIG.maxTokens uses the || operator which can incorrectly treat valid
falsy numeric values (e.g. 0) as missing; change the fallback to the nullish
coalescing operator so: set TEST_CONFIG.maxTokens =
PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] ?? 1024, referencing
TEST_CONFIG.maxTokens, PROVIDER_MAX_TOKENS and TEST_CONFIG.provider to locate
the line and ensure unknown-provider or null/undefined values fall back to 1024
without masking valid falsy numbers.
src/lib/utils/modelChoices.ts (1)

255-283: Prefer enum constants over raw model strings in TOP_MODELS_CONFIG.

Since DeepSeekModels and NvidiaNimModels are already imported, using them here will prevent drift between enum definitions and CLI choices.

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

In `@src/lib/utils/modelChoices.ts` around lines 255 - 283, Replace raw model
string literals in the TOP_MODELS_CONFIG entries for AIProviderName.DEEPSEEK and
AIProviderName.NVIDIA_NIM with the corresponding enum constants from
DeepSeekModels and NvidiaNimModels (e.g., use the DeepSeekModels member that
maps to "deepseek-chat" and the member for "deepseek-reasoner", and use the
appropriate NvidiaNimModels members for the listed NVIDIA model strings). Update
the objects under AIProviderName.DEEPSEEK and AIProviderName.NVIDIA_NIM to
reference these enum values for the model property while keeping the description
fields unchanged so the config remains consistent with the imported enums.
docs/provider-integration/07-implementation-order.md (1)

91-105: Minor: Add blank lines around fenced code blocks for markdown lint compliance.

The code blocks in the Milestone 4 section should have blank lines before/after them per MD031.

📝 Proposed fix
 4. Validate retry-on-400:
+
    ```bash
    # Use a model that doesn't support reasoning_budget
    pnpm run cli generate "Hi" --provider nvidia-nim --model google/gemma-3-27b-it --thinking-level high
    # Should succeed (logs: "NIM rejected reasoning_budget; retrying without it")
    ```
+
 5. Validate vision model:
+
    ```bash
    pnpm run cli generate "Describe" --provider nvidia-nim --model meta/llama-3.2-90b-vision-instruct --image ./test.jpg
    ```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/provider-integration/07-implementation-order.md` around lines 91 - 105,
In the "Milestone 4" section the fenced bash blocks under "Validate
retry-on-400" and "Validate vision model" are missing blank lines before/after
the ```bash fences which violates MD031; update the markdown so there is an
empty line immediately before each opening ```bash and an empty line immediately
after each closing ``` (i.e., add a blank line before the retry-on-400 code
block and a blank line after it, and likewise add a blank line before the vision
model code block) to satisfy the linter.
test/continuous-test-suite-providers.ts (1)

841-843: Optional: rename the function to match the new provider-agnostic scope.

The function name testGemini3DisableTools is now narrower than behavior after Line 851 switched to buildBaseSDKOptions(). A rename like testDisableTools would reduce future confusion.

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

In `@test/continuous-test-suite-providers.ts` around lines 841 - 843, Rename the
function testGemini3DisableTools to a provider-agnostic name such as
testDisableTools and update all references/call sites accordingly (e.g., any
invocations or exports that currently reference testGemini3DisableTools); ensure
the async signature async function testDisableTools(sdk: NeuroLink):
Promise<boolean | null> and any related documentation/comments reflect the new
name so it matches the new buildBaseSDKOptions() generic behavior.
test/run-provider-matrix.sh (1)

132-133: Matrix rows should come from the same suite source as execution.

Line 132 appends context memory outside SUITES, so the matrix includes suites that were never run. Consider deriving matrix rows solely from SUITES (or explicitly include these suites in execution) to avoid drift.

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

In `@test/run-provider-matrix.sh` around lines 132 - 133, The matrix generator
currently iterates with "for suite in $SUITES context memory;" causing rows for
"context" and "memory" that may not be executed; change the loop to derive rows
only from the SUITES variable or ensure SUITES explicitly contains "context" and
"memory". Update the loop that references SUITES (the "for suite in ..." line)
to either iterate solely over "$SUITES" or modify how SUITES is constructed so
it includes the additional suites, ensuring matrix rows and execution sources
stay in sync.
🤖 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/adapters/providerImageAdapter.ts`:
- Around line 446-447: The provider entry for "deepseek" is an empty readonly
string[] which indicates no vision models, but supportsVision("deepseek")
currently only checks for provider presence and thus returns true when model is
omitted; update the supportsVision function to also verify the provider's model
list is non-empty when model is undefined or omitted (e.g., check
adapterProviders[provider]?.length > 0 or providerModelsMap[provider]?.length >
0) so that a provider with an empty model array (like deepseek) correctly
reports no vision support.

In `@src/lib/core/baseProvider.ts`:
- Around line 60-64: The constructor-instantiated handlers (TelemetryHandler,
MessageBuilder) cache the initial this.modelName, so later auto-discovery
assignments to modelName leave handlers stale; add a method
updateResolvedModelName(newName: string) on the class (BaseProvider) that sets
this.modelName and updates or reinitializes any composed handlers' cached model
name (e.g., call handler.setModelName/new initializer on TelemetryHandler and
MessageBuilder or recreate them), and change auto-discovery providers to call
updateResolvedModelName(...) instead of assigning this.modelName directly so
telemetry and message metadata stay in sync.

In `@src/lib/providers/deepseek.ts`:
- Around line 155-174: The streamText call in deepseek provider forces thinking
mode by always setting providerOptions.openai.thinking when modelName !==
DeepSeekModels.DEEPSEEK_REASONER; change this to opt-in like other providers by
only adding the thinking block when options.thinkingConfig?.enabled is
truthy—i.e., in the streamText invocation (inside the method that builds
messages/tools/etc.) replace the unconditional providerOptions with a
conditional spread or ternary that includes { openai: { thinking: { type:
"enabled" } } } only if options.thinkingConfig?.enabled is true, leaving
providerOptions undefined otherwise so deepseek-chat respects the existing
StreamOptions opt-in pattern.

In `@src/lib/providers/llamaCpp.ts`:
- Around line 110-119: getAvailableModels currently calls fetch directly (using
this.baseURL) without createProxyFetch or a timeout, so discovery/health probes
bypass any auth/proxy routing and can hang; update getAvailableModels to use the
provider's createProxyFetch(...) (so the configured apiKey/proxy is applied) and
perform the request with an AbortController timeout tied to the same timeout
configuration used elsewhere, and apply the same change to the health/checking
methods referenced around lines 309-337 (use createProxyFetch and an
AbortController timeout) so model discovery and health probes honor proxy/auth
and request timeouts.
- Around line 28-47: The current makeLoggingFetch logs up to 800 chars of
upstream response bodies (variable body) which can leak sensitive prompt/tool
data; modify makeLoggingFetch so it does not include raw response.body in logs —
either remove body from logger.warn, replace it with a sanitized/hashed
indicator (e.g., a fixed "[redacted]" or a hash/length), and keep only
non-sensitive metadata (response.status and url) in the warn call; ensure you
update the logger.warn invocation inside the async fetch wrapper (the closure
using base, response, clone, body, and provider) to avoid storing or emitting
the raw body text.

In `@src/lib/providers/lmStudio.ts`:
- Around line 165-168: The reassignment this.modelName = modelToUse fixes only
the returned model string but leaves helper objects created in BaseProvider
(MessageBuilder, TelemetryHandler, GenerationHandler, etc.) holding the old
model name; after resolving modelToUse you must update those cached handlers so
spans/logging/middleware reflect the discovered name—either re-create them with
the new modelToUse or call their setter/update methods (e.g., replace
this.messageBuilder, this.telemetryHandler, this.generationHandler with new
instances constructed using modelToUse or invoke a setModelName/updateModel
method if available) so generate()/stream() and telemetry emit the correct model
name.
- Around line 117-126: The getAvailableModels method uses bare fetch(url) which
bypasses the provider's configured fetch (e.g., createProxyFetch or this.fetch)
and thus ignores API-key/proxy behavior; replace the direct fetch calls in
getAvailableModels (and the similar calls at lines referenced 321-325) to use
the provider's configured fetch/client method (the same function used elsewhere
in this class, e.g., this.fetch or createProxyFetch-returned function) so the
requests include auth headers and proxy handling, and surface errors the same
way as other provider requests.
- Around line 37-47: The current error log prints raw request payload via
reqBody; replace that with a redacted payload plus size metadata: compute a
bodyLength from init?.body (for strings use .length or Buffer.byteLength
equivalent, for non-strings mark as binary) and set reqBody to a constant like
"<redacted>" or "<binary>" instead of slicing user content, then update the
logger.error call (around the base(input, init) response handling and variables
reqBody, logger.error) to include only status, url, payloadKind
(redacted/binary), and bodyLength rather than any raw payload substring.

In `@src/lib/providers/nvidiaNim.ts`:
- Around line 228-233: The code currently reads
options.thinkingConfig?.thinkingLevel so calls that pass thinkingLevel directly
are ignored; update the logic in the block that computes thinkingEnabled and
calls buildNvidiaNimExtraBody to first read options.thinkingLevel (falling back
to options.thinkingConfig?.thinkingLevel) so that thinkingLevel values
('minimal'|'low'|'medium'|'high') are respected, and ensure
buildNvidiaNimExtraBody is invoked with that computed thinkingEnabled and
options.maxTokens so chat_template_kwargs/reasoning_budget get added for normal
generate/stream calls.

In `@src/lib/utils/modelChoices.ts`:
- Around line 255-295: DEFAULT_MODELS is missing entries for the newly added
providers so getDefaultModel() returns undefined; update the DEFAULT_MODELS map
to include keys for AIProviderName.DEEPSEEK, AIProviderName.NVIDIA_NIM,
AIProviderName.LM_STUDIO, and AIProviderName.LLAMACPP with appropriate default
model strings (e.g., the primary model used in modelChoices for DEEPSEEK and
NVIDIA_NIM, and an empty string or auto-discover marker for LM_STUDIO and
LLAMACPP). Locate the DEFAULT_MODELS constant and add these provider entries
(matching the models listed in the modelChoices array) so
getDefaultModel(provider) returns a valid default for each new provider.

In `@test/continuous-test-suite-context.ts`:
- Around line 615-618: The provider selection currently falls back to "vertex"
unless process.env.TEST_PROVIDER is set, causing flags like
--provider=deepseek/--provider=lm-studio to be ignored; update the branches that
compute useTestProvider, provider, and model to use TEST_CONFIG.provider (not
the literal "vertex") when useTestProvider is true and to use the corresponding
VERTEX_FLASH_TEST_MODEL or VERTEX_PRO_TEST_MODEL only when useTestProvider is
false; specifically adjust the blocks that define useTestProvider, provider, and
model (and the analogous Pro variant using VERTEX_PRO_TEST_MODEL) so provider =
useTestProvider ? TEST_CONFIG.provider : "vertex" remains but model =
useTestProvider ? TEST_CONFIG.model : VERTEX_FLASH_TEST_MODEL (and the Pro
variant) uses TEST_CONFIG.provider-driven logic rather than falling back
incorrectly.

In `@test/continuous-test-suite-new-providers.ts`:
- Around line 467-475: The current skip logic treats any provider named
"lm-studio" or "llamacpp" as vision-capable even when the loaded model is
text-only; update the gate to also check whether the loaded model is multimodal
before running C1. Modify the conditional that uses p.visionModel and p.name
(the block that logs `[${p.name}] C1 image.basic` and calls record(p.name,
null)) to require an explicit multimodal flag or capability on the provider
object (e.g., p.isMultimodal or p.visionModel.type === "multimodal") for
"lm-studio" and "llamacpp" as well, and only skip when neither a vision model
nor a multimodal-capable model is present.
- Around line 392-442: The tests currently allow a pass even if the model never
calls the provided tools because B1 returns invoked || Boolean(res?.content) and
B2 returns invoked || true; update both to actually prove tool execution: in the
"B1 tools.generate.custom" test (sdk.generate with tools.get_weather and invoked
flag) require that invoked is true or that res contains the tool output fields
(e.g., city/temperatureC/conditions) before returning success; in the "B2
tools.stream.custom" test (sdk.stream with tools.get_time and invoked flag)
remove the unconditional true and require either invoked is true or the drained
stream contains evidence of the tool output (e.g., ISO time field) so the test
fails if the model never executed the tool.

In `@test/continuous-test-suite-observability.ts`:
- Line 74: The timeout assignment using parseInt(process.env.TEST_TIMEOUT_MS ||
"90000", 10) can produce NaN if TEST_TIMEOUT_MS is present but invalid; update
the code that sets the timeout property to validate the parsed value (e.g.,
assign to a temporary like parsedTimeout = parseInt(..., 10) and then set
timeout = Number.isFinite(parsedTimeout) ? parsedTimeout : 90000) so the timeout
field never becomes NaN and always falls back to the intended default when
parsing fails.

In `@test/run-provider-matrix.sh`:
- Around line 85-86: The script hardcodes gtimeout in the command that runs
suites (the pnpm run "test:$suite" invocation) which breaks on systems that only
have timeout; replace the direct gtimeout invocation with a portable
lookup/fallback (e.g., resolve a TIMEOUT_CMD from command -v gtimeout || command
-v timeout and use that variable where gtimeout is called) so the runner uses
whichever timeout binary exists. Also fix the suite/matrix mismatch: either stop
appending "context memory" to the matrix list or ensure those entries are
actually executed by adding them into the SUITES list used by the main loop (the
SUITES variable or the loop that iterates suites for pnpm run), so matrix cells
correspond to executed suites.

---

Outside diff comments:
In `@test/continuous-test-suite-mcp.ts`:
- Around line 152-154: The fallback token budget currently uses 4096 when a
provider key is missing; update the default to match other suites by changing
the fallback in the assignment to TEST_CONFIG.maxTokens (which uses
PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] || 4096) to 1024 so unmapped providers
use the suite-wide default; verify this code path where TEST_CONFIG.model is set
via resolveTestModel(TEST_CONFIG.provider) remains unchanged.

---

Minor comments:
In `@docs/provider-integration/00-architecture.md`:
- Around line 95-106: The fenced TypeScript block documenting
ProviderConfigOptions violates markdownlint MD031 because it is not separated
from the preceding list item by blank lines; update the markdown around the
fenced block (the snippet following the bullet mentioning createMistralConfig(),
createOpenAIConfig(), etc.) by inserting an empty line before the opening ```ts
and an empty line after the closing ``` so the fenced block is surrounded by
blank lines and MD031 is resolved.

In `@docs/provider-integration/06-testing.md`:
- Around line 36-43: The documentation text is inaccurate about the provider
test loop control flow: update the narrative to reflect that the actual loop in
the test suite iterates ALL_PROVIDERS and calls generate("Hi") / stream("Hi")
first, and determines SKIP from the observed error handling rather than by
calling validateConfiguration() up-front; mention the actual behavior of
validateConfiguration() (used for probes by local providers like LM
Studio/llama.cpp) and clarify that SKIP is derived from generated errors rather
than a prior boolean check from validateConfiguration().
- Around line 58-65: The sample uses result?.text but the project’s active
suites expect result.content; update the nl.generate usage and assertions to
consume result.content instead of result.text (e.g., change the assertion to
assert(typeof result?.content === "string", "should return content")) for the
shown block and the other occurrences noted (lines referenced in the comment) so
examples match the continuous suites' response field naming; keep the same call
to nl.generate and its parameters (provider "deepseek", credentials, maxTokens).

In `@docs/provider-integration/10-test-results-final.md`:
- Around line 77-83: The table's pass-rate uses a different denominator than
"Total sub-tests"; add an explicit "SKIP" column in the header and for each
provider row (e.g., DeepSeek, NVIDIA NIM, LM Studio, llama.cpp) populate SKIP
counts, then recalc Pass-rate using a clear formula in a footnote or header:
"Pass-rate = PASS / (Total sub-tests - SKIP) × 100%". Also update the table
caption or a short note to define the formula so readers won't misinterpret the
denominator.

In `@docs/provider-integration/12-pr-analysis.md`:
- Line 12: Update the text fragment "- **13 new markdown docs** (~150KB)" to use
proper noun casing by replacing "markdown" with "Markdown" so it reads "- **13
new Markdown docs** (~150KB)"; locate the exact string in the document (the line
containing "**13 new markdown docs**") and make the single-word casing change.
- Around line 229-230: Update the release note that incorrectly states
getOutputReserve clamping is "NOT in this PR"—change the wording to reflect that
the clamping fix was implemented in this PR by referencing the change in
src/lib/constants/contextWindows.ts (the getOutputReserve behavior now clamps
maxTokens to contextWindow * 0.8) so the documentation no longer contradicts the
code.

In `@docs/provider-integration/README.md`:
- Line 3: Replace the hardcoded absolute path in the README line that reads
"Implementation documentation for porting four providers from
`/Users/sachinsharma/Developer/temp/ai-coder/free-claude-code` into Neurolink"
with a repo-relative path or a generic placeholder (e.g.,
"./examples/free-claude-code" or "<path/to/source-repo>") so the docs are
portable and do not leak local filesystem details; update any identical
occurrences elsewhere in the file.
- Around line 35-45: Update the "Status" section table to reflect the current PR
state: change the rows for "DeepSeek implementation", "NVIDIA NIM
implementation", "LM Studio implementation", "llama.cpp implementation", "Shared
changes (types/CLI/etc)" and "Tests" from "⏳ Spec only" to statuses indicating
they are implemented (e.g., "✅ Implemented" or "✅ Implemented, tests passing")
and mention that provider implementations and test coverage are included in this
PR so readers are not misled by stale information.

In `@src/cli/factories/commandFactory.ts`:
- Line 3931: The bash completion list in the COMPREPLY assignment (the string
containing compgen -W "...") is missing several valid entries from
commonOptions.provider.choices; update that quoted word list to exactly match
all accepted provider names and aliases (e.g., add openrouter,
openai-compatible, google-ai-studio, sagemaker and aliases like ds, lmstudio,
llama.cpp/llamacpp) so the COMPREPLY/compgen -W string is kept in sync with
commonOptions.provider.choices; ensure any future changes are mirrored here or
consider deriving the list from the same provider choices source to avoid drift.

In `@test/continuous-test-suite-credentials.ts`:
- Around line 539-542: The credential/provider alignment check is comparing raw
provider keys to normalized entries like "nvidianim" and "lmstudio", causing
mismatches for keys such as "nvidia-nim" and "lm-studio"; update the comparison
in the provider alignment routine (the credential/provider alignment check
function) to compare normalized forms by applying the same normalization to both
sides (e.g., s => s.toLowerCase().replace(/-/g, '')) so "nvidia-nim" equals
"nvidianim" and "lm-studio" equals "lmstudio" wherever the array entries
"nvidianim" and "lmstudio" are referenced (also apply same fix to the other
checks in the same block that cover the entries at the later range).

In `@test/continuous-test-suite-new-providers.ts`:
- Around line 377-388: The catch block currently returns early when zod is
missing, skipping later tests like "B4 tools.disable"; instead remove the early
"return" and leave zodMod set to null after logging and marking B1 tests as
SKIP. Ensure the B1 test logic that uses zodMod (references: zodMod variable,
PROVIDERS loop, logTest, record) is guarded to skip when zodMod is null, so
execution continues to run subsequent sections (including B4 tools.disable) even
if zod is not installed.

---

Nitpick comments:
In `@docs/provider-integration/07-implementation-order.md`:
- Around line 91-105: In the "Milestone 4" section the fenced bash blocks under
"Validate retry-on-400" and "Validate vision model" are missing blank lines
before/after the ```bash fences which violates MD031; update the markdown so
there is an empty line immediately before each opening ```bash and an empty line
immediately after each closing ``` (i.e., add a blank line before the
retry-on-400 code block and a blank line after it, and likewise add a blank line
before the vision model code block) to satisfy the linter.

In `@src/lib/utils/modelChoices.ts`:
- Around line 255-283: Replace raw model string literals in the
TOP_MODELS_CONFIG entries for AIProviderName.DEEPSEEK and
AIProviderName.NVIDIA_NIM with the corresponding enum constants from
DeepSeekModels and NvidiaNimModels (e.g., use the DeepSeekModels member that
maps to "deepseek-chat" and the member for "deepseek-reasoner", and use the
appropriate NvidiaNimModels members for the listed NVIDIA model strings). Update
the objects under AIProviderName.DEEPSEEK and AIProviderName.NVIDIA_NIM to
reference these enum values for the model property while keeping the description
fields unchanged so the config remains consistent with the imported enums.

In `@test/continuous-test-suite-ppt.ts`:
- Line 1616: The assignment to TEST_CONFIG.maxTokens uses the || operator which
can incorrectly treat valid falsy numeric values (e.g. 0) as missing; change the
fallback to the nullish coalescing operator so: set TEST_CONFIG.maxTokens =
PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] ?? 1024, referencing
TEST_CONFIG.maxTokens, PROVIDER_MAX_TOKENS and TEST_CONFIG.provider to locate
the line and ensure unknown-provider or null/undefined values fall back to 1024
without masking valid falsy numbers.

In `@test/continuous-test-suite-providers.ts`:
- Around line 841-843: Rename the function testGemini3DisableTools to a
provider-agnostic name such as testDisableTools and update all references/call
sites accordingly (e.g., any invocations or exports that currently reference
testGemini3DisableTools); ensure the async signature async function
testDisableTools(sdk: NeuroLink): Promise<boolean | null> and any related
documentation/comments reflect the new name so it matches the new
buildBaseSDKOptions() generic behavior.

In `@test/continuous-test-suite-session-memory-bugs.ts`:
- Line 1013: Replace the fallback used when setting TEST_CONFIG.maxTokens so it
uses the nullish coalescing operator instead of logical OR; specifically update
the assignment that references PROVIDER_MAX_TOKENS and TEST_CONFIG.provider (the
expression assigning TEST_CONFIG.maxTokens) to use ?? for fallback to 1024 so
undefined/null provider entries don't incorrectly fall back when falsy values
are valid.

In `@test/continuous-test-suite-tts.ts`:
- Line 1874: Replace the loose fallback using || when setting
TEST_CONFIG.maxTokens so that zero values are preserved and unexpected/missing
provider keys are detected: read PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] into
a variable, use the nullish coalescing operator (??) to fall back to 1024 only
when the lookup is null or undefined, and add an explicit branch that signals an
unknown provider (e.g., warn/log or set a distinct sentinel) when the lookup is
undefined; update the assignment site (TEST_CONFIG.maxTokens and the
PROVIDER_MAX_TOKENS lookup) accordingly.

In `@test/run-provider-matrix.sh`:
- Around line 132-133: The matrix generator currently iterates with "for suite
in $SUITES context memory;" causing rows for "context" and "memory" that may not
be executed; change the loop to derive rows only from the SUITES variable or
ensure SUITES explicitly contains "context" and "memory". Update the loop that
references SUITES (the "for suite in ..." line) to either iterate solely over
"$SUITES" or modify how SUITES is constructed so it includes the additional
suites, ensuring matrix rows and execution sources stay in sync.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22dff8ae-fade-45af-92e0-82850a05ee7d

📥 Commits

Reviewing files that changed from the base of the PR and between 971bce7 and 3b9d46a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (49)
  • .env.example
  • docs/provider-integration/00-architecture.md
  • docs/provider-integration/01-shared-changes.md
  • docs/provider-integration/02-deepseek.md
  • docs/provider-integration/03-nvidia-nim.md
  • docs/provider-integration/04-lm-studio.md
  • docs/provider-integration/05-llamacpp.md
  • docs/provider-integration/06-testing.md
  • docs/provider-integration/07-implementation-order.md
  • docs/provider-integration/08-feature-matrix.md
  • docs/provider-integration/09-test-suite-spec.md
  • docs/provider-integration/10-test-results-final.md
  • docs/provider-integration/11-test-failure-investigation.md
  • docs/provider-integration/12-pr-analysis.md
  • docs/provider-integration/13-code-review.md
  • docs/provider-integration/README.md
  • package.json
  • src/cli/factories/commandFactory.ts
  • src/lib/adapters/providerImageAdapter.ts
  • src/lib/constants/contextWindows.ts
  • src/lib/constants/enums.ts
  • src/lib/core/baseProvider.ts
  • src/lib/factories/providerRegistry.ts
  • src/lib/providers/deepseek.ts
  • src/lib/providers/index.ts
  • src/lib/providers/llamaCpp.ts
  • src/lib/providers/lmStudio.ts
  • src/lib/providers/nvidiaNim.ts
  • src/lib/types/providers.ts
  • src/lib/utils/modelChoices.ts
  • src/lib/utils/pricing.ts
  • src/lib/utils/providerConfig.ts
  • test/continuous-test-suite-client.ts
  • test/continuous-test-suite-context.ts
  • test/continuous-test-suite-credentials.ts
  • test/continuous-test-suite-evaluation-scoring.ts
  • test/continuous-test-suite-evaluation.ts
  • test/continuous-test-suite-mcp-http.ts
  • test/continuous-test-suite-mcp.ts
  • test/continuous-test-suite-media-gen.ts
  • test/continuous-test-suite-memory.ts
  • test/continuous-test-suite-new-providers.ts
  • test/continuous-test-suite-observability.ts
  • test/continuous-test-suite-ppt.ts
  • test/continuous-test-suite-providers.ts
  • test/continuous-test-suite-session-memory-bugs.ts
  • test/continuous-test-suite-tts.ts
  • test/continuous-test-suite-workflow.ts
  • test/run-provider-matrix.sh

Comment thread src/lib/adapters/providerImageAdapter.ts
Comment thread src/lib/core/baseProvider.ts
Comment thread src/lib/providers/deepseek.ts Outdated
Comment on lines +155 to +174
const isReasoner = this.modelName === DeepSeekModels.DEEPSEEK_REASONER;
const result = await streamText({
model,
messages,
temperature: options.temperature,
maxOutputTokens: options.maxTokens,
tools,
stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
abortSignal: composeAbortSignals(
options.abortSignal,
timeoutController?.controller.signal,
),
providerOptions: !isReasoner
? {
openai: {
thinking: { type: "enabled" },
},
}
: undefined,

ghost Apr 28, 2026

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the deepseek.ts file and examine it
fd -type f -name "deepseek.ts"

Repository: juspay/neurolink

Length of output: 231


🏁 Script executed:

# Check the file size to determine how to read it
wc -l src/lib/providers/deepseek.ts

Repository: juspay/neurolink

Length of output: 94


🏁 Script executed:

# Read the full deepseek.ts file to understand the context
cat -n src/lib/providers/deepseek.ts

Repository: juspay/neurolink

Length of output: 11175


🏁 Script executed:

# Check what models are defined in the enum or constants
rg -A 20 "DeepSeekModels" src/lib/providers/ --type ts

Repository: juspay/neurolink

Length of output: 4017


🏁 Script executed:

# Search for thinking-related configurations in other providers for comparison
rg "thinking" src/lib/providers/ --type ts -B 2 -A 2

Repository: juspay/neurolink

Length of output: 6346


🏁 Script executed:

# Check the StreamOptions type to see if thinkingConfig is supported
rg -A 30 "type StreamOptions" src/lib/types/ -m 1

Repository: juspay/neurolink

Length of output: 1997


🏁 Script executed:

# Search for test files related to deepseek
fd -type f -name "*deepseek*" src/

Repository: juspay/neurolink

Length of output: 231


🏁 Script executed:

# Check if there are comments or documentation about DeepSeek thinking requirements
rg "thinking" src/lib/providers/deepseek.ts -B 5 -A 5

Repository: juspay/neurolink

Length of output: 388


🏁 Script executed:

# Check the DeepSeek enum to understand the model types
rg -A 10 "enum DeepSeekModels\|DeepSeekModels = " src/lib/constants/

Repository: juspay/neurolink

Length of output: 42


🏁 Script executed:

# Find the enums file and check DeepSeekModels definition
rg "DEEPSEEK_REASONER\|DEEPSEEK_CHAT" src/lib/constants/

Repository: juspay/neurolink

Length of output: 42


🏁 Script executed:

# Check full StreamOptions type definition
sed -n '1,200p' src/lib/types/stream.ts | cat -n

Repository: juspay/neurolink

Length of output: 7398


🏁 Script executed:

# Search for thinkingConfig in StreamOptions
rg "thinkingConfig" src/lib/types/ -B 2 -A 2

Repository: juspay/neurolink

Length of output: 6214


🏁 Script executed:

# Check the full thinkingConfig type definition
cat -n src/lib/types/config.ts | head -100

Repository: juspay/neurolink

Length of output: 4347


🏁 Script executed:

# Verify how other providers check thinkingConfig before enabling
grep -A 5 "thinkingConfig?.enabled" src/lib/providers/*.ts

Repository: juspay/neurolink

Length of output: 1001


Remove forced thinking mode for deepseek-chat requests—make it opt-in like other providers.

The code unconditionally enables thinking: { type: "enabled" } for all non-deepseek-reasoner models without checking options.thinkingConfig?.enabled. This forces thinking mode on plain deepseek-chat requests even when users never requested it. Other providers (googleAiStudio, googleVertex) use an opt-in pattern: ...(options.thinkingConfig?.enabled && { ... }). StreamOptions already supports thinkingConfig for opt-in control, so align DeepSeek with that design.

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

In `@src/lib/providers/deepseek.ts` around lines 155 - 174, The streamText call in
deepseek provider forces thinking mode by always setting
providerOptions.openai.thinking when modelName !==
DeepSeekModels.DEEPSEEK_REASONER; change this to opt-in like other providers by
only adding the thinking block when options.thinkingConfig?.enabled is
truthy—i.e., in the streamText invocation (inside the method that builds
messages/tools/etc.) replace the unconditional providerOptions with a
conditional spread or ternary that includes { openai: { thinking: { type:
"enabled" } } } only if options.thinkingConfig?.enabled is true, leaving
providerOptions undefined otherwise so deepseek-chat respects the existing
StreamOptions opt-in pattern.

Comment on lines +28 to +47
const makeLoggingFetch = (provider: string): typeof fetch => {
const base = createProxyFetch();
return (async (input, init) => {
const response = await base(input, init);
if (!response.ok) {
const clone = response.clone();
const body = await clone.text().catch(() => "<unreadable>");
const url =
typeof input === "string"
? input
: input instanceof URL
? input.toString()
: input.url;
logger.warn(`[${provider}] upstream ${response.status}`, {
url,
body: body.slice(0, 800),
});
}
return response;
}) as typeof fetch;

ghost Apr 28, 2026

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 | 🟠 Major

Avoid logging raw upstream error bodies here.

The response body from an OpenAI-compatible server can include echoed prompt fragments or tool payloads. Logging up to 800 characters at warn level is a privacy footgun for production deployments.

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

In `@src/lib/providers/llamaCpp.ts` around lines 28 - 47, The current
makeLoggingFetch logs up to 800 chars of upstream response bodies (variable
body) which can leak sensitive prompt/tool data; modify makeLoggingFetch so it
does not include raw response.body in logs — either remove body from
logger.warn, replace it with a sanitized/hashed indicator (e.g., a fixed
"[redacted]" or a hash/length), and keep only non-sensitive metadata
(response.status and url) in the warn call; ensure you update the logger.warn
invocation inside the async fetch wrapper (the closure using base, response,
clone, body, and provider) to avoid storing or emitting the raw body text.

Comment thread src/lib/providers/llamaCpp.ts Outdated
Comment thread test/continuous-test-suite-context.ts Outdated
Comment thread test/continuous-test-suite-new-providers.ts Outdated
Comment thread test/continuous-test-suite-new-providers.ts Outdated
Comment thread test/continuous-test-suite-observability.ts Outdated
Comment thread test/run-provider-matrix.sh Outdated
@murdore

ghost commented Apr 28, 2026

Copy link
Copy Markdown
Contributor Author

Pushed amend (f73e1db) addressing all 15 actionable CodeRabbit comments from the prior review:

Critical correctness

  • baseProvider.refreshHandlersForModel(model) rebuilds MessageBuilder/StreamHandler/TelemetryHandler/GenerationHandler/Utilities so post-discovery model name reaches pricing/spans/logs (lmStudio + llamaCpp).
  • providerImageAdapter.supportsVision returns false for empty supported-models list (prevents DeepSeek false-positive).
  • nvidiaNim reasoning_budget now reads options.thinkingLevel with thinkingConfig?.thinkingLevel fallback.
  • modelChoices.DEFAULT_MODELS gained entries for the 4 new providers (empty string for auto-discovery).
  • continuous-test-suite-context Vertex/Compaction branches now pivot on TEST_CONFIG.provider === 'vertex' instead of requiring process.env.TEST_PROVIDER.
  • B1 (toolUsage) and B2 (streamWithTools) tests now require actual toolCalls/toolsUsed (no longer pass when model ignores tools).
  • Local vision skip uses VISION_HINTS substring matching against the discovered model id.

Privacy / security

  • Upstream non-2xx body capture in deepseek/nvidiaNim/lmStudio/llamaCpp gated behind NEUROLINK_DEBUG_HTTP=1; default is status/url/reqSize only.

Auto-discovery hardening

  • getAvailableModels and validateConfiguration in lmStudio/llamaCpp now use createProxyFetch(), attach Authorization header when configured, and apply 5s timeout.

Configurability

  • LM_STUDIO_API_KEY / LLAMACPP_API_KEY env vars + credentials.lmStudio.apiKey / credentials.llamacpp.apiKey for reverse-proxy/auth-gated setups; .env.example updated.

Test/infra

  • continuous-test-suite-observability parseInt now NaN-guarded (Number.isFinite && > 0, fallback 90 000 ms).
  • continuous-test-suite-mcp aligned to 1024 fallback.
  • continuous-test-suite-credentials recognises kebab-case provider aliases via norm().
  • run-provider-matrix.sh detects gtimeout/timeout (Linux/macOS portability).

Polish

  • CLI bash completion list expanded with all 13 provider IDs and aliases.
  • Doc cleanups in docs/provider-integration/ (README path scrub, 12-pr-analysis Markdown casing + addressed-items section).

Single-commit policy followed (--amend + --force-with-lease).

@coderabbitai full review please

@coderabbitai

ghost commented Apr 28, 2026

Copy link
Copy Markdown

@murdore — thank you for the detailed amend summary! The fixes look thorough across all categories:

  • Correctness: handler refresh after model auto-discovery, vision false-positive guard, thinkingLevel fallback chain, DEFAULT_MODELS entries, and tightened tool-call assertions are all solid improvements.
  • Privacy/Security: gating the upstream response body behind NEUROLINK_DEBUG_HTTP=1 is exactly the right pattern.
  • Hardening: createProxyFetch() + Authorization header + 5 s timeout on discovery calls closes the credential-leak and hang risks cleanly.
  • Portability: gtimeout/timeout detection in run-provider-matrix.sh is a nice cross-platform touch.

Kicking off a fresh full review against commit f73e1db4 now.


🧠 Learnings used
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-24T03:50:12.966Z
Learning: Applies to src/lib/types/providers.ts : Add new provider names to `AIProviderName` enum in `src/lib/types/providers.ts`

Learnt from: vigneshJuspay
Repo: juspay/neurolink PR: 693
File: src/lib/core/baseProvider.ts:490-517
Timestamp: 2025-12-18T15:13:28.435Z
Learning: In juspay/neurolink TTS integration (PR `#693`), when options.provider is "auto" and passed to TTSProcessor.synthesize, it will fail automatically during handler lookup since only concrete providers ("google-ai", "vertex") are registered as TTS handlers. No explicit validation against "auto" is needed—the implicit failure at handler registration lookup is by design.

Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-24T03:50:12.966Z
Learning: Applies to src/lib/providers/**/*.ts : Extend `BaseProvider` when implementing a new AI provider

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.

Learnt from: Rahul-2903-juspay
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-03-10T05:42:30.185Z
Learning: PR `#862` in juspay/neurolink (docs/features/video-director-mode.md) is a planning/design document (RFC-style spec) for the Video Director Mode feature, not yet implemented. The feature involves multi-clip Veo 3.1 video generation with AI transitions via first-and-last-frame interpolation, using directorPipeline.ts, frameExtractor.ts, and videoMerger.ts (planned files). Frame extraction and video merging library choices are still unspecified in the design.
✅ Actions performed

Full review triggered.

@github-actions

ghost commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

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

⚠️ Outside diff range comments (2)
test/continuous-test-suite-credentials.ts (1)

564-568: ⚠️ Potential issue | 🔴 Critical

Fix undefined variable in assertion message (availableLower).

Line 567 references availableLower, but that identifier is never declared in scope. This will throw at runtime when the assertion path executes.

💡 Proposed fix
     assert(
       matchCount >= 5,
       `Expected at least 5 credential keys to match registered providers. ` +
-        `Got ${matchCount} matches. Available providers: ${availableLower.join(", ")}`,
+        `Got ${matchCount} matches. Available providers: ${available.join(", ")}`,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-credentials.ts` around lines 564 - 568, The
assertion references an undefined identifier availableLower; fix by using a
valid variable or computing the lowercase names before the assert. For example,
create a local array (e.g., const availableLower = availableProviders.map(p =>
p.toLowerCase()) or use the existing availableProviders/available variable and
join it directly) and then use that in the message string so the assert in the
block that checks matchCount uses a defined value.
test/continuous-test-suite-context.ts (1)

611-712: ⚠️ Potential issue | 🟡 Minor

Disambiguate the duplicated “Vertex Flash” test label.

Line 611 and Line 825 both log as SDK Generate - Context Compaction Vertex Flash, but they exercise different paths. This makes failures ambiguous in summary logs and CI artifacts.

Suggested fix
 async function testContextCompactionVertex(
   sdk: NeuroLink,
 ): Promise<boolean | null> {
-  logTest("SDK Generate - Context Compaction Vertex Flash", "TESTING");
+  const testName = "SDK Generate - Context Compaction Provider-Agnostic";
+  logTest(testName, "TESTING");
   try {
@@
-          logTest(
-            "SDK Generate - Context Compaction Vertex Flash",
-            "SKIP",
-            msg,
-          );
+          logTest(testName, "SKIP", msg);
@@
-        logTest(
-          "SDK Generate - Context Compaction Vertex Flash",
-          "FAIL",
-          "Final generate() returned empty after conversation",
-        );
+        logTest(testName, "FAIL", "Final generate() returned empty after conversation");
@@
-      logTest(
-        "SDK Generate - Context Compaction Vertex Flash",
-        "PASS",
-        `Final call succeeded after ${totalTurns} turns on ${provider}${usage?.promptTokens ? `, promptTokens=${usage.promptTokens}` : ""}`,
-      );
+      logTest(testName, "PASS", `Final call succeeded after ${totalTurns} turns on ${provider}${usage?.promptTokens ? `, promptTokens=${usage.promptTokens}` : ""}`);
@@
-        logTest("SDK Generate - Context Compaction Vertex Flash", "SKIP", msg);
+        logTest(testName, "SKIP", msg);
@@
-      logTest(
-        "SDK Generate - Context Compaction Vertex Flash",
-        "FAIL",
-        `Final generate() failed: ${msg}`,
-      );
+      logTest(testName, "FAIL", `Final generate() failed: ${msg}`);
@@
-      logTest("SDK Generate - Context Compaction Vertex Flash", "SKIP", msg);
+      logTest(testName, "SKIP", msg);
@@
-    logTest("SDK Generate - Context Compaction Vertex Flash", "FAIL", msg);
+    logTest(testName, "FAIL", msg);

Also applies to: 825-922

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

In `@test/continuous-test-suite-context.ts` around lines 611 - 712, The test uses
the same human-readable label string in multiple logTest calls ("SDK Generate -
Context Compaction Vertex Flash"), making results ambiguous; update the label
strings passed to logTest (and any matching final messages) to unique names for
the two different test paths (e.g., change the first block's label in the loop
and final-result logTest calls and the second block's corresponding logTest
calls) so each invocation of logTest and its returns reference distinct
identifiers; locate all usages of logTest in the SDK Generate - Context
Compaction test blocks and rename their label arguments accordingly.
♻️ Duplicate comments (2)
src/lib/providers/deepseek.ts (1)

164-183: ⚠️ Potential issue | 🟠 Major

Thinking mode is still unconditionally enabled for deepseek-chat.

Per the past review comment, lines 177-183 unconditionally enable thinking: { type: "enabled" } for all non-deepseek-reasoner models. This forces thinking mode on requests even when the user didn't opt in via options.thinkingConfig?.enabled.

Other providers like googleAiStudio and googleVertex use an opt-in pattern:

...(options.thinkingConfig?.enabled && { thinking: { ... } })

If this is intentional DeepSeek-specific behavior (matching the source repo's request.py), please add a comment explaining the rationale. Otherwise, make thinking opt-in for consistency.

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

In `@src/lib/providers/deepseek.ts` around lines 164 - 183, The streamText call
currently forces thinking mode for non-reasoner DeepSeek models by setting
providerOptions.openai.thinking unconditionally; change this to be opt-in like
other providers by only including the thinking object when
options.thinkingConfig?.enabled is true (i.e. replace the unconditional block in
the providerOptions branch with a conditional spread based on
options.thinkingConfig?.enabled), or if the unconditional behavior is
intentional add a brief comment above the providerOptions block explaining why
DeepSeek differs; key symbols: streamText, providerOptions,
DeepSeekModels.DEEPSEEK_REASONER, isReasoner, and
options.thinkingConfig?.enabled.
test/run-provider-matrix.sh (1)

143-163: ⚠️ Potential issue | 🟡 Minor

Matrix includes context and memory suites that are never executed.

Line 143 iterates $SUITES context memory, but the main execution loop (line 73) only runs suites from $SUITES. Since no test:context or test:memory scripts are executed, these rows will always show "—" in the matrix output, which is misleading.

Either:

  1. Remove context memory from line 143 if they're not intended to be run, or
  2. Add them to SUITES on line 31 if they should be executed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/run-provider-matrix.sh` around lines 143 - 163, The matrix printing loop
is iterating "for suite in $SUITES context memory" but the runner only executes
suites from the SUITES variable, so "context" and "memory" are never run and
always show empty results; fix by either removing the hardcoded "context memory"
from the loop (leave it as "for suite in $SUITES") or add "context" and "memory"
to the SUITES definition so they are actually executed (update the SUITES
variable where it is defined) so the matrix reflects real test runs.
🧹 Nitpick comments (7)
src/lib/utils/modelChoices.ts (1)

255-283: Prefer enum constants over raw model strings in top-model config.

These entries are valid, but using DeepSeekModels/NvidiaNimModels constants here would reduce drift between enums, defaults, and CLI choices.

♻️ Refactor sketch
   [AIProviderName.DEEPSEEK]: [
-    { model: "deepseek-chat", description: "DeepSeek-V3 general chat" },
+    { model: DeepSeekModels.DEEPSEEK_CHAT, description: "DeepSeek-V3 general chat" },
     {
-      model: "deepseek-reasoner",
+      model: DeepSeekModels.DEEPSEEK_REASONER,
       description: "DeepSeek-R1 reasoning (slower, deeper)",
     },
   ],
   [AIProviderName.NVIDIA_NIM]: [
     {
-      model: "meta/llama-3.3-70b-instruct",
+      model: NvidiaNimModels.LLAMA_3_3_70B_INSTRUCT,
       description: "Recommended - Llama 3.3 70B",
     },
     {
-      model: "nvidia/llama-3.3-nemotron-super-49b-v1",
+      model: NvidiaNimModels.LLAMA_3_3_NEMOTRON_SUPER_49B_V1,
       description: "Nemotron Super (reasoning)",
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/utils/modelChoices.ts` around lines 255 - 283, Replace the raw model
string literals under AIProviderName.DEEPSEEK and AIProviderName.NVIDIA_NIM with
the corresponding enum/constants (DeepSeekModels and NvidiaNimModels) so the
choices reference the canonical constants instead of hard-coded strings; update
each object’s model field to use the appropriate enum member (e.g.,
DeepSeekModels.DEEPSEEK_CHAT, DeepSeekModels.DEEPSEEK_REASONER,
NvidiaNimModels.LLAMA_3_3_70B, etc.) while keeping descriptions intact so
modelChoices.ts stays in sync with the enums.
test/continuous-test-suite-credentials.ts (1)

545-562: Prefer explicit alias mapping over symmetric substring matching.

p.includes(kn) || kn.includes(p) can create false positives (for example, matching openai when validating openaicompatible), which weakens this test’s signal.

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

In `@test/continuous-test-suite-credentials.ts` around lines 545 - 562, The
symmetric substring matching in matchCount (using p.includes(kn) ||
kn.includes(p)) causes false positives; replace it with an explicit alias
mapping: keep the normalization function norm and availableNormalized, then
create a map of normalized credential keys-to-allowed provider tokens (e.g., for
"openaicompatible" map to ["openai","openaicompatible"], for "googleaistudio"
map to ["google","gemini","googleaistudio"], etc.), and change the filter for
credKeys to check that the normalized key kn strictly equals or is listed in
that alias map for some provider token p (or that p equals/contains an allowed
token), rather than using kn.includes(p) or p.includes(kn); update matchCount to
use this explicit alias check so only intended aliases match (refer to norm,
availableNormalized, matchCount, credKeys, available).
docs/provider-integration/02-deepseek.md (1)

42-291: Documentation code snippet has drifted from the actual implementation.

The embedded code example differs from the actual src/lib/providers/deepseek.ts in several ways:

  • Missing makeLoggingFetch() wrapper for debug logging
  • Missing OTEL span wrapping via withClientSpan
  • Missing insufficient balance (402) error handling in formatProviderError
  • Missing .chat() call on the OpenAI client (uses deepseek(this.modelName) instead of deepseek.chat(this.modelName))

Consider updating the snippet to match the actual implementation or adding a note that the code is illustrative and may differ from the final version.

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

In `@docs/provider-integration/02-deepseek.md` around lines 42 - 291, The
documentation snippet for DeepSeekProvider is out of sync with the real
implementation: update the example to use makeLoggingFetch when constructing the
OpenAI client, wrap calls that call the provider in withClientSpan for OTEL
tracing, add 402/insufficient balance handling to formatProviderError (similar
to the rate-limit and auth checks), and use deepseek.chat(this.modelName)
instead of deepseek(this.modelName); locate these changes around the OpenAI
client construction (createOpenAI/createProxyFetch usage), the method
formatProviderError, and the constructor assignment to this.model to keep the
docs consistent with src/lib/providers/deepseek.ts.
docs/provider-integration/04-lm-studio.md (1)

127-138: Documentation snippet uses bare fetch() for model discovery.

The actual implementation uses createProxyFetch() with Authorization headers and a 5s timeout for hardened auto-discovery. Consider updating this snippet to reflect the production code pattern.

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

In `@docs/provider-integration/04-lm-studio.md` around lines 127 - 138, The
snippet for getAvailableModels uses a bare fetch() which differs from
production; replace it to use the project's createProxyFetch() with the same
Authorization header handling and 5s timeout used elsewhere so auto-discovery is
hardened. Update the getAvailableModels implementation to obtain a proxied fetch
(via createProxyFetch(this.baseURL) or the project's factory), call that instead
of fetch, ensure the Authorization header from the client (or token) is applied,
and set the request timeout to 5000ms; keep existing error handling and the
mapping from ModelsResponse to string[] intact.
docs/provider-integration/05-llamacpp.md (1)

125-135: Documentation snippet uses bare fetch() for model discovery, but hardened implementation should use proxy-aware fetch.

Per the PR objectives, auto-discovery was hardened to use createProxyFetch() with Authorization headers and 5s timeout. This documentation snippet still shows bare fetch(url) without auth headers or timeout. Update the snippet to reflect the hardened pattern for consistency.

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

In `@docs/provider-integration/05-llamacpp.md` around lines 125 - 135, The
getAvailableModels method uses bare fetch; replace it with the proxy-aware fetch
pattern used elsewhere: create proxy headers including Authorization (via
createProxyHeaders or equivalent) and call createProxyFetch(...) with a 5s
timeout before requesting `${this.baseURL.replace(/\/$/, "")}/models`; then
check response.ok and parse JSON as ModelsResponse as before. Update
getAvailableModels to build proxy headers, call createProxyFetch to perform the
request with the 5s timeout, and keep the existing error message and return
data.data.map(m => m.id).
docs/provider-integration/07-implementation-order.md (1)

90-105: Consider adding blank lines around fenced code blocks in numbered lists.

Markdownlint flags these code blocks (lines 91-94, 96-100, 102-104) for not being surrounded by blank lines. While most renderers handle this correctly, adding blank lines improves compatibility.

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

In `@docs/provider-integration/07-implementation-order.md` around lines 90 - 105,
Add a blank line before and after each fenced code block in the numbered list
examples (the three triple-backtick blocks shown under steps "Validate base
case", "Validate retry-on-400", and "Validate vision model") so that each
```bash ... ``` block is separated by an empty line from the surrounding list
text; update the fenced blocks in
docs/provider-integration/07-implementation-order.md accordingly to satisfy
markdownlint and improve renderer compatibility.
src/lib/providers/nvidiaNim.ts (1)

299-301: Type result explicitly to clear the Biome error.

Line 299 still introduces an implicit any, so this provider can fail lint on noImplicitAnyLet.

Proposed fix
-      let result;
+      let result: Awaited<ReturnType<typeof callStream>>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/providers/nvidiaNim.ts` around lines 299 - 301, The local variable
result is implicitly any; explicitly type it to match callStream's return type
to satisfy noImplicitAnyLet—locate the let result declaration in the nvidiaNim
provider and change it to use an explicit type derived from callStream (for
example, the awaited return type or the concrete interface the function returns)
so downstream code uses a typed value; update any nearby references if necessary
(target symbols: result, callStream).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/provider-integration/00-architecture.md`:
- Around line 96-106: The fenced TypeScript code block containing the
ProviderConfigOptions type is missing surrounding blank lines and triggers
markdownlint MD031; fix this by adding a blank line immediately before the
opening ```ts and a blank line immediately after the closing ``` so the
ProviderConfigOptions block is separated from adjacent text and satisfies MD031.

In `@docs/provider-integration/01-shared-changes.md`:
- Around line 106-117: Update the docs and the example `NeurolinkCredentials`
type to document that LM Studio and llama.cpp can accept optional API keys for
proxied local deployments: add `lmStudio?: { baseURL?: string; apiKey?: string
};` and `llamacpp?: { baseURL?: string; apiKey?: string };` to the type example
and explicitly mention the corresponding env vars `LM_STUDIO_API_KEY` and
`LLAMACPP_API_KEY` (and that `credentials.lmStudio.apiKey` /
`credentials.llamacpp.apiKey` are supported), and mirror this clarification in
the other affected section (lines ~450-486) so auth for proxied local providers
is not silently dropped.

In `@docs/provider-integration/03-nvidia-nim.md`:
- Around line 445-447: The fenced code block starting with "```bash" containing
the grep command (grep -rn "providerOptions" src/lib/providers/openRouter.ts
src/lib/providers/litellm.ts | head -10) in the pre-implementation verification
section needs surrounding blank lines to satisfy MD031; add one blank line
immediately before the opening ```bash and one blank line immediately after the
closing ``` to remove the lint warning.

In `@docs/provider-integration/08-feature-matrix.md`:
- Around line 15-19: The feature matrix has inconsistent failure counts between
the summary table row for "pnpm run test:new-providers" and later narratives for
providers (specifically "DeepSeek" and "NVIDIA NIM"); reconcile them by either
updating the table counts to match the detailed section numbers (or vice versa)
or clearly annotate each count with run identifiers/timestamps (e.g., "run on
YYYY-MM-DD" or "latest run") so both the table row for "pnpm run
test:new-providers" and the narrative mentions of "DeepSeek 11 failures" and
"NVIDIA NIM remaining 5 failures" (and other occurrences in lines 29-44)
consistently refer to the same run or explicitly state they are different runs.

In `@docs/provider-integration/12-pr-analysis.md`:
- Around line 216-246: The doc contains a contradiction: question 5 asks whether
to keep the cast-based workaround `(this as unknown as { modelName: string
}).modelName = modelToUse;` in lmStudio.ts/llamaCpp.ts, but the "Items addressed
by this PR" section states BaseProvider.modelName is no longer readonly and
refreshHandlersForModel(model) replaces that workaround; remove or mark question
5 as closed and update the text to reflect that the workaround was replaced by
making BaseProvider.modelName mutable and adding refreshHandlersForModel(model),
and ensure mentions of lmStudio.ts and llamaCpp.ts no longer suggest the cast
escape is pending.

In `@docs/provider-integration/13-code-review.md`:
- Around line 146-147: Update the security note to reflect the implemented
logging gate: state that makeLoggingFetch only writes request body excerpts to
stderr for non-2xx responses and only when the NEUROLINK_DEBUG_HTTP=1
environment variable is set; mention that this behavior affects paid providers
(DeepSeek/NIM) so reviewers understand the log is already gated rather than
being a pending consideration. Refer to makeLoggingFetch and the
NEUROLINK_DEBUG_HTTP env var when making this wording change.

In `@docs/provider-integration/README.md`:
- Around line 27-35: Replace the hard-coded workstation path
"/Users/sachinsharma/Developer/temp/ai-coder/free-claude-code/" in the README's
"Source-of-truth references" section with a portable upstream reference: mention
the upstream repo name and commit hashes and use relative paths from that
upstream root (e.g., "/free-claude-code/..." as a conceptual root) instead of
the local absolute path; update the explanatory sentence so it reads that paths
in /free-claude-code/... refer to the upstream repo at the listed commit(s)
rather than a local workstation.

In `@src/lib/adapters/providerImageAdapter.ts`:
- Around line 454-462: The vision-model heuristics are too permissive: remove
"llama-3.2" and "llama3.2" from the vision-capable lists for the "lm-studio" and
"llamacpp" entries so text-only Llama 3.2 models aren't treated as
vision-capable; update the mapping in providerImageAdapter.ts (the object
literal with keys "lm-studio" and "llamacpp") to only include true vision
indicators (e.g., "llava", "qwen2-vl", "qwen2.5-vl", "phi-3-vision") and leave
out any plain "llama-3.2"/"llama3.2" tokens.

In `@src/lib/core/baseProvider.ts`:
- Around line 161-202: refreshHandlersForModel rebuilds handlers when a model is
discovered but doesn't update the already-active root span's model attribute,
leaving GEN_AI_MODEL stale; inside refreshHandlersForModel after setting
this.modelName and rebuilding handlers, fetch the current active span from the
neurolink context (e.g. via this.neurolink?.getActiveSpan() or the equivalent
helper on Neurolink) and call setAttribute(GEN_AI_MODEL, model) (or
span.setAttribute with the existing GEN_AI_MODEL constant) only when a span
exists so the running root span reflects the discovered model.

In `@src/lib/providers/llamaCpp.ts`:
- Around line 191-203: The span and message formatting are created before the
model autodiscovery runs, so call the model-refresh path first: invoke
refreshHandlersForModel() (or call getAISDKModel*/the method that triggers it)
to resolve this.modelName/this.discoveredModel before entering withClientSpan
and before any buildMessagesForStream calls; update the stream entry point (the
method that currently calls withClientSpan/executeStreamInner) to await the
refresh/resolve step first so OTEL attributes and message/tool/image formatting
use the correct discovered model (apply same change to the similar block
referenced around lines 226-227).

In `@src/lib/types/providers.ts`:
- Around line 174-199: SupportedModelName is missing the new provider model
enums, causing type gaps; import DeepSeekModels, NvidiaNimModels,
LMStudioModels, and LlamaCppModels and add them into the SupportedModelName
union so the union includes DeepSeekModels | NvidiaNimModels | LMStudioModels |
LlamaCppModels alongside existing members; update the imports at the top (where
other enums are imported) and extend the SupportedModelName type declaration to
include these four enum types so AIModelProviderConfig.models and
ProviderAttempt.model accept the new providers.

In `@test/continuous-test-suite-new-providers.ts`:
- Around line 149-159: ProviderUnderTest lacks a model field so code that uses
p.model fails; add an optional loadedModel?: string to ProviderUnderTest and
update probeLocalServer (or add a new fetchModels function) so it returns both
availability and the discovered model ID (e.g., { available: boolean,
loadedModel?: string }) instead of just boolean; then change callers that
currently use p.visionModel ?? p.model to use p.visionModel ?? p.loadedModel
(and ensure probeLocalServer populates loadedModel for lm-studio and llamacpp by
querying the /models endpoint).

---

Outside diff comments:
In `@test/continuous-test-suite-context.ts`:
- Around line 611-712: The test uses the same human-readable label string in
multiple logTest calls ("SDK Generate - Context Compaction Vertex Flash"),
making results ambiguous; update the label strings passed to logTest (and any
matching final messages) to unique names for the two different test paths (e.g.,
change the first block's label in the loop and final-result logTest calls and
the second block's corresponding logTest calls) so each invocation of logTest
and its returns reference distinct identifiers; locate all usages of logTest in
the SDK Generate - Context Compaction test blocks and rename their label
arguments accordingly.

In `@test/continuous-test-suite-credentials.ts`:
- Around line 564-568: The assertion references an undefined identifier
availableLower; fix by using a valid variable or computing the lowercase names
before the assert. For example, create a local array (e.g., const availableLower
= availableProviders.map(p => p.toLowerCase()) or use the existing
availableProviders/available variable and join it directly) and then use that in
the message string so the assert in the block that checks matchCount uses a
defined value.

---

Duplicate comments:
In `@src/lib/providers/deepseek.ts`:
- Around line 164-183: The streamText call currently forces thinking mode for
non-reasoner DeepSeek models by setting providerOptions.openai.thinking
unconditionally; change this to be opt-in like other providers by only including
the thinking object when options.thinkingConfig?.enabled is true (i.e. replace
the unconditional block in the providerOptions branch with a conditional spread
based on options.thinkingConfig?.enabled), or if the unconditional behavior is
intentional add a brief comment above the providerOptions block explaining why
DeepSeek differs; key symbols: streamText, providerOptions,
DeepSeekModels.DEEPSEEK_REASONER, isReasoner, and
options.thinkingConfig?.enabled.

In `@test/run-provider-matrix.sh`:
- Around line 143-163: The matrix printing loop is iterating "for suite in
$SUITES context memory" but the runner only executes suites from the SUITES
variable, so "context" and "memory" are never run and always show empty results;
fix by either removing the hardcoded "context memory" from the loop (leave it as
"for suite in $SUITES") or add "context" and "memory" to the SUITES definition
so they are actually executed (update the SUITES variable where it is defined)
so the matrix reflects real test runs.

---

Nitpick comments:
In `@docs/provider-integration/02-deepseek.md`:
- Around line 42-291: The documentation snippet for DeepSeekProvider is out of
sync with the real implementation: update the example to use makeLoggingFetch
when constructing the OpenAI client, wrap calls that call the provider in
withClientSpan for OTEL tracing, add 402/insufficient balance handling to
formatProviderError (similar to the rate-limit and auth checks), and use
deepseek.chat(this.modelName) instead of deepseek(this.modelName); locate these
changes around the OpenAI client construction (createOpenAI/createProxyFetch
usage), the method formatProviderError, and the constructor assignment to
this.model to keep the docs consistent with src/lib/providers/deepseek.ts.

In `@docs/provider-integration/04-lm-studio.md`:
- Around line 127-138: The snippet for getAvailableModels uses a bare fetch()
which differs from production; replace it to use the project's
createProxyFetch() with the same Authorization header handling and 5s timeout
used elsewhere so auto-discovery is hardened. Update the getAvailableModels
implementation to obtain a proxied fetch (via createProxyFetch(this.baseURL) or
the project's factory), call that instead of fetch, ensure the Authorization
header from the client (or token) is applied, and set the request timeout to
5000ms; keep existing error handling and the mapping from ModelsResponse to
string[] intact.

In `@docs/provider-integration/05-llamacpp.md`:
- Around line 125-135: The getAvailableModels method uses bare fetch; replace it
with the proxy-aware fetch pattern used elsewhere: create proxy headers
including Authorization (via createProxyHeaders or equivalent) and call
createProxyFetch(...) with a 5s timeout before requesting
`${this.baseURL.replace(/\/$/, "")}/models`; then check response.ok and parse
JSON as ModelsResponse as before. Update getAvailableModels to build proxy
headers, call createProxyFetch to perform the request with the 5s timeout, and
keep the existing error message and return data.data.map(m => m.id).

In `@docs/provider-integration/07-implementation-order.md`:
- Around line 90-105: Add a blank line before and after each fenced code block
in the numbered list examples (the three triple-backtick blocks shown under
steps "Validate base case", "Validate retry-on-400", and "Validate vision
model") so that each ```bash ... ``` block is separated by an empty line from
the surrounding list text; update the fenced blocks in
docs/provider-integration/07-implementation-order.md accordingly to satisfy
markdownlint and improve renderer compatibility.

In `@src/lib/providers/nvidiaNim.ts`:
- Around line 299-301: The local variable result is implicitly any; explicitly
type it to match callStream's return type to satisfy noImplicitAnyLet—locate the
let result declaration in the nvidiaNim provider and change it to use an
explicit type derived from callStream (for example, the awaited return type or
the concrete interface the function returns) so downstream code uses a typed
value; update any nearby references if necessary (target symbols: result,
callStream).

In `@src/lib/utils/modelChoices.ts`:
- Around line 255-283: Replace the raw model string literals under
AIProviderName.DEEPSEEK and AIProviderName.NVIDIA_NIM with the corresponding
enum/constants (DeepSeekModels and NvidiaNimModels) so the choices reference the
canonical constants instead of hard-coded strings; update each object’s model
field to use the appropriate enum member (e.g., DeepSeekModels.DEEPSEEK_CHAT,
DeepSeekModels.DEEPSEEK_REASONER, NvidiaNimModels.LLAMA_3_3_70B, etc.) while
keeping descriptions intact so modelChoices.ts stays in sync with the enums.

In `@test/continuous-test-suite-credentials.ts`:
- Around line 545-562: The symmetric substring matching in matchCount (using
p.includes(kn) || kn.includes(p)) causes false positives; replace it with an
explicit alias mapping: keep the normalization function norm and
availableNormalized, then create a map of normalized credential keys-to-allowed
provider tokens (e.g., for "openaicompatible" map to
["openai","openaicompatible"], for "googleaistudio" map to
["google","gemini","googleaistudio"], etc.), and change the filter for credKeys
to check that the normalized key kn strictly equals or is listed in that alias
map for some provider token p (or that p equals/contains an allowed token),
rather than using kn.includes(p) or p.includes(kn); update matchCount to use
this explicit alias check so only intended aliases match (refer to norm,
availableNormalized, matchCount, credKeys, available).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f54a5dbc-6172-4f40-a738-4c8f19e8e65b

📥 Commits

Reviewing files that changed from the base of the PR and between 971bce7 and f73e1db.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (49)
  • .env.example
  • docs/provider-integration/00-architecture.md
  • docs/provider-integration/01-shared-changes.md
  • docs/provider-integration/02-deepseek.md
  • docs/provider-integration/03-nvidia-nim.md
  • docs/provider-integration/04-lm-studio.md
  • docs/provider-integration/05-llamacpp.md
  • docs/provider-integration/06-testing.md
  • docs/provider-integration/07-implementation-order.md
  • docs/provider-integration/08-feature-matrix.md
  • docs/provider-integration/09-test-suite-spec.md
  • docs/provider-integration/10-test-results-final.md
  • docs/provider-integration/11-test-failure-investigation.md
  • docs/provider-integration/12-pr-analysis.md
  • docs/provider-integration/13-code-review.md
  • docs/provider-integration/README.md
  • package.json
  • src/cli/factories/commandFactory.ts
  • src/lib/adapters/providerImageAdapter.ts
  • src/lib/constants/contextWindows.ts
  • src/lib/constants/enums.ts
  • src/lib/core/baseProvider.ts
  • src/lib/factories/providerRegistry.ts
  • src/lib/providers/deepseek.ts
  • src/lib/providers/index.ts
  • src/lib/providers/llamaCpp.ts
  • src/lib/providers/lmStudio.ts
  • src/lib/providers/nvidiaNim.ts
  • src/lib/types/providers.ts
  • src/lib/utils/modelChoices.ts
  • src/lib/utils/pricing.ts
  • src/lib/utils/providerConfig.ts
  • test/continuous-test-suite-client.ts
  • test/continuous-test-suite-context.ts
  • test/continuous-test-suite-credentials.ts
  • test/continuous-test-suite-evaluation-scoring.ts
  • test/continuous-test-suite-evaluation.ts
  • test/continuous-test-suite-mcp-http.ts
  • test/continuous-test-suite-mcp.ts
  • test/continuous-test-suite-media-gen.ts
  • test/continuous-test-suite-memory.ts
  • test/continuous-test-suite-new-providers.ts
  • test/continuous-test-suite-observability.ts
  • test/continuous-test-suite-ppt.ts
  • test/continuous-test-suite-providers.ts
  • test/continuous-test-suite-session-memory-bugs.ts
  • test/continuous-test-suite-tts.ts
  • test/continuous-test-suite-workflow.ts
  • test/run-provider-matrix.sh

Comment on lines +96 to +106
```ts
type ProviderConfigOptions = {
providerName: string;
envVarName: string;
setupUrl: string;
description: string;
instructions: string[];
fallbackEnvVars?: string[];
};
```

ghost Apr 29, 2026

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

Fix markdownlint MD031 around fenced code formatting.

The fenced block around Line 96 should be surrounded by blank lines to satisfy MD031 and keep docs lint-clean.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 96-96: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

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

In `@docs/provider-integration/00-architecture.md` around lines 96 - 106, The
fenced TypeScript code block containing the ProviderConfigOptions type is
missing surrounding blank lines and triggers markdownlint MD031; fix this by
adding a blank line immediately before the opening ```ts and a blank line
immediately after the closing ``` so the ProviderConfigOptions block is
separated from adjacent text and satisfies MD031.

Comment thread docs/provider-integration/01-shared-changes.md
Comment on lines +445 to +447
```bash
grep -rn "providerOptions" src/lib/providers/openRouter.ts src/lib/providers/litellm.ts | head -10
```

ghost Apr 29, 2026

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

Add blank lines around the fenced block near pre-implementation verification.

This section triggers markdownlint MD031; adding the surrounding blank lines will fix lint noise in docs CI.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 445-445: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

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

In `@docs/provider-integration/03-nvidia-nim.md` around lines 445 - 447, The
fenced code block starting with "```bash" containing the grep command (grep -rn
"providerOptions" src/lib/providers/openRouter.ts src/lib/providers/litellm.ts |
head -10) in the pre-implementation verification section needs surrounding blank
lines to satisfy MD031; add one blank line immediately before the opening
```bash and one blank line immediately after the closing ``` to remove the lint
warning.

Comment thread docs/provider-integration/08-feature-matrix.md Outdated
Comment thread docs/provider-integration/12-pr-analysis.md
Comment thread src/lib/adapters/providerImageAdapter.ts Outdated
Comment thread src/lib/core/baseProvider.ts
Comment thread src/lib/providers/llamaCpp.ts
Comment thread src/lib/types/providers.ts
Comment thread test/continuous-test-suite-new-providers.ts
@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

Round 1 (HEAD 3320248) — addressed 11 of 12 actionable comments from the second CodeRabbit pass:

Major / Critical

  • src/lib/types/providers.ts: SupportedModelName extended with DeepSeekModels | NvidiaNimModels | LMStudioModels | LlamaCppModels. AIModelProviderConfig.models and ProviderAttempt.model now type-cover the new providers.
  • src/lib/core/baseProvider.ts: refreshHandlersForModel now also pushes GEN_AI_MODEL onto the active span via trace.getSpan(context.active())?.setAttribute(...), so first-call OTEL attributes track the discovered model.
  • src/lib/providers/lmStudio.ts + llamaCpp.ts: executeStream now await this.getAISDKModel() BEFORE entering withClientSpan, so span attributes / message-builder / image-adapter all see the resolved model on first request. getAISDKModel is idempotent (returns cached this.model) so the extra call is free on subsequent requests.
  • src/lib/adapters/providerImageAdapter.ts: vision heuristics for lm-studio / llamacpp no longer match bare llama-3.2 / llama3.2 — only llama-3.2-11b-vision, llama-3.2-90b-vision, vision-instruct, llava, qwen2-vl, qwen2.5-vl, phi-3-vision. Text-only Llama 3.2 1B/3B no longer falsely report vision support.
  • test/continuous-test-suite-new-providers.ts: probeLocalServer now returns { available, loadedModel? }. Added loadedModel to ProviderUnderTest and populated it for lm-studio/llamacpp from the /v1/models probe. p.visionModel ?? p.loadedModel now sees the actual loaded model id; the C1 vision skip predicate works correctly.

Docs

  • docs/provider-integration/README.md: replaced workstation-specific path with portable upstream reference.
  • docs/provider-integration/12-pr-analysis.md: marked Q5 (cast-escape question) as resolved with cross-link to "Items addressed by this PR".
  • docs/provider-integration/13-code-review.md: security note rewritten in past tense — makeLoggingFetch body capture is gated behind NEUROLINK_DEBUG_HTTP=1.
  • docs/provider-integration/08-feature-matrix.md: aggregate-row counts and narrative-section counts annotated as Run-A vs Run-B (different test environments) rather than reconciled (since they're real historical artifacts).
  • docs/provider-integration/01-shared-changes.md: documented apiKey?: string for lmStudio/llamacpp + LM_STUDIO_API_KEY / LLAMACPP_API_KEY env vars in both the NeurolinkCredentials example and .env.example.

Skipped (not safe to apply)

  • markdownlint MD031 in 00-architecture.md L96-105 and 03-nvidia-nim.md L445-447 — both fenced blocks live INSIDE list items. Adding the suggested blank lines would break the list-continuation indentation. The lint warning is acceptable cost for keeping the lists intact.

Verification: pnpm run check 0 errors, pnpm run lint 0 errors (18 pre-existing warnings unchanged), pnpm run format applied.

Single-commit policy followed (--amend + --force-with-lease).

@coderabbitai full review please

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown

@murdore — thank you for the thorough Round 1 summary. The fixes are well-targeted:

  • The pre-span await this.getAISDKModel() pattern in lmStudio.ts / llamaCpp.ts is the right call — resolving the model before entering withClientSpan ensures all span attributes, message-builder, and image-adapter references see the correct model identity on the very first request, and the idempotency guarantee keeps subsequent calls free.
  • Pushing GEN_AI_MODEL onto the active span inside refreshHandlersForModel closes the OTEL attribute gap cleanly without requiring callers to know about the attribute.
  • The tightened vision substring list (dropping bare llama-3.2 / llama3.2) is exactly the right fix for the false-positive — vision-capable substrings now require an explicit -vision, llava, qwen2*-vl, or phi-3-vision suffix.
  • The probeLocalServer → { available, loadedModel? } refactor makes C1 skip logic data-driven rather than heuristic.
  • The MD031 skip rationale is sound — blank lines inside list items break CommonMark list continuation, so accepting the lint warning is the correct trade-off.

Kicking off a fresh full review against 33202487 now.


🧠 Learnings used
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.
✅ Actions performed

Full review triggered.

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Caution

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

⚠️ Outside diff range comments (1)
test/continuous-test-suite-credentials.ts (1)

545-568: ⚠️ Potential issue | 🔴 Critical

Fix the stale availableLower reference before merging.

Line 567 still interpolates availableLower, but that variable was renamed to availableNormalized above. This will break the test file at compile/runtime.

🐛 Proposed fix
-        `Got ${matchCount} matches. Available providers: ${availableLower.join(", ")}`,
+        `Got ${matchCount} matches. Available providers: ${availableNormalized.join(", ")}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-credentials.ts` around lines 545 - 568, The
assertion message still references the old variable name availableLower which no
longer exists; update the message to use the normalized array name
availableNormalized (or otherwise format a readable list from
availableNormalized) so the string interpolation in the assert uses a valid
symbol; locate the assert block that uses matchCount and replace availableLower
with availableNormalized (or a joined form like availableNormalized.join(", "))
to avoid the compile/runtime error.
♻️ Duplicate comments (5)
docs/provider-integration/10-test-results-final.md (1)

46-50: ⚠️ Potential issue | 🟡 Minor

This fix description is outdated after the amend.

The final code no longer relies on a TS cast escape to persist the discovered model. Keeping that explanation here will send reviewers after a bug that has already been removed.

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

In `@docs/provider-integration/10-test-results-final.md` around lines 46 - 50, The
docs note for "Bug `#5` — Provider model name not persisting after auto-discovery"
contains an outdated claim about persisting the discovered model using a
"TS-cast escape" to write to this.modelName; update the paragraph to remove the
TS-cast escape explanation and instead state that the discovered model is now
persisted without mentioning the cast, referencing the fields
this.discoveredModel and this.modelName so reviewers know which behavior
changed.
docs/provider-integration/00-architecture.md (1)

95-105: ⚠️ Potential issue | 🟡 Minor

Add blank lines around this fenced block.

This nested code fence still triggers markdownlint MD031, so the doc remains lint-dirty.

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

In `@docs/provider-integration/00-architecture.md` around lines 95 - 105, Add
blank lines before and after the fenced TypeScript block that documents
ProviderConfigOptions so the snippet for
createMistralConfig()/createOpenAIConfig() is separated from surrounding text;
specifically, ensure there is an empty line immediately above the ```ts fence
and an empty line immediately below it to satisfy markdownlint MD031 (the block
that contains the type ProviderConfigOptions should be surrounded by those blank
lines).
test/run-provider-matrix.sh (1)

143-163: ⚠️ Potential issue | 🟡 Minor

Drop context / memory from the matrix or execute them too.

The runner never invokes those suites because SUITES does not include them, so these rows are guaranteed to show placeholders and make the matrix look incomplete.

Suggested fix
-  for suite in $SUITES context memory; do
+  for suite in $SUITES; do
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/run-provider-matrix.sh` around lines 143 - 163, The matrix script
iterates a hardcoded list "for suite in $SUITES context memory;" which prints
rows for "context" and "memory" even though the runner never adds them to
SUITES; update the loop to either remove the hardcoded "context memory" (so only
suites in SUITES are printed) or ensure SUITES is augmented with "context" and
"memory" before the loop (e.g., append them to the SUITES variable), and keep
the existing file-checking/case logic (look for the loop that references SUITES
and the subsequent f="$RESULTS/$p/${suite}.summary" handling to locate where to
change the iteration).
docs/provider-integration/03-nvidia-nim.md (1)

445-447: ⚠️ Potential issue | 🟡 Minor

Add blank lines around the fenced bash block (MD031).

The fenced code block is still missing required surrounding blank lines, so markdownlint will keep warning.

Proposed doc fix
 2. That version supports `providerOptions.openai.body` to pass arbitrary extra body fields. Look at:
+
    ```bash
    grep -rn "providerOptions" src/lib/providers/openRouter.ts src/lib/providers/litellm.ts | head -10
    ```
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/provider-integration/03-nvidia-nim.md` around lines 445 - 447, Add
required blank lines before and after the fenced bash block containing the grep
command to satisfy MD031; locate the fenced block that starts with ```bash and
the line containing grep -rn "providerOptions" src/lib/providers/openRouter.ts
src/lib/providers/litellm.ts | head -10 and insert one empty line immediately
above the opening ```bash and one empty line immediately below the closing ```
so the code fence is surrounded by blank lines.
src/lib/providers/deepseek.ts (1)

177-183: ⚠️ Potential issue | 🟠 Major

Make DeepSeek thinking mode opt-in instead of forced for deepseek-chat.

At Line 177–Line 183, providerOptions.openai.thinking is enabled for every non-reasoner request. This overrides caller intent and can increase latency/cost for plain chat flows.

Proposed fix
-        providerOptions: !isReasoner
-          ? {
-              openai: {
-                thinking: { type: "enabled" },
-              },
-            }
-          : undefined,
+        providerOptions:
+          !isReasoner && options.thinkingConfig?.enabled
+            ? {
+                openai: {
+                  thinking: { type: "enabled" },
+                },
+              }
+            : undefined,
#!/bin/bash
# Verify thinking-mode gating consistency across providers
rg -n -C2 'thinkingConfig|providerOptions|thinking' \
  src/lib/providers/deepseek.ts \
  src/lib/providers/googleAiStudio.ts \
  src/lib/providers/googleVertex.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/providers/deepseek.ts` around lines 177 - 183, The code currently
forces providerOptions.openai.thinking = { type: "enabled" } whenever isReasoner
is false, which makes DeepSeek always use thinking mode; change this to be
opt-in by only setting providerOptions.openai.thinking when the call explicitly
requests thinking (e.g., via a passed-in flag or in the caller's options)
instead of defaulting for non-reasoner flows; update the logic around
providerOptions and isReasoner in the DeepSeek handler (deepseek-chat entry) so
that if no explicit thinking request is present you leave
providerOptions.openai.thinking undefined, and preserve existing behavior when
thinking is explicitly enabled.
🧹 Nitpick comments (3)
src/cli/factories/commandFactory.ts (1)

60-87: Centralize the provider token list.

These identifiers are now duplicated in the setup wizard and completion script too, so this list can drift over time. A shared PROVIDER_CHOICES constant (or a helper derived from the provider registry) would keep generate, setup, and shell completion in sync.

♻️ Suggested refactor
+const PROVIDER_CHOICES = [
+  "auto",
+  "openai",
+  "openai-compatible",
+  "openrouter",
+  "or",
+  "bedrock",
+  "vertex",
+  "googleVertex",
+  "anthropic",
+  "anthropic-subscription",
+  "azure",
+  "google-ai",
+  "google-ai-studio",
+  "huggingface",
+  "ollama",
+  "mistral",
+  "litellm",
+  "sagemaker",
+  "deepseek",
+  "ds",
+  "nvidia-nim",
+  "nim",
+  "lm-studio",
+  "lmstudio",
+  "llamacpp",
+  "llama.cpp",
+] as const;

Then reuse PROVIDER_CHOICES in the CLI builders and completion generator.

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

In `@src/cli/factories/commandFactory.ts` around lines 60 - 87, The provider
choice array in commandFactory.ts is duplicated across CLI builders and
completion scripts; extract that array into a shared constant (e.g.,
PROVIDER_CHOICES) exported from a single module and replace the inline choices
array in commandFactory.ts (the provider: { choices: [...] } block) and other
callers (generate/setup builders and the shell completion generator) to import
and use PROVIDER_CHOICES so all CLI builders and completion code reference the
same source of truth.
src/lib/utils/providerConfig.ts (1)

425-495: Either wire these helpers in or drop them for now.

providerRegistry.ts still hardcodes the DeepSeek/NVIDIA NIM/LM Studio/llama.cpp defaults and setup text, so these creators are currently dead code. Keeping them unconsumed will let the registry and setup guidance drift.

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

In `@src/lib/utils/providerConfig.ts` around lines 425 - 495, The new helper
functions createDeepSeekConfig, createNvidiaNimConfig, createLmStudioConfig, and
createLlamaCppConfig are dead code because providerRegistry.ts still hardcodes
those provider defaults; either wire these helpers into the registry (replace
the hardcoded DeepSeek/NVIDIA NIM/LM Studio/llama.cpp entries in
providerRegistry.ts to call the respective functions) or remove the helper
functions to avoid drifting setup text; update any setup/guidance rendering to
consume the ProviderConfigOptions returned by these functions so the registry
and UI stay consistent.
src/lib/providers/deepseek.ts (1)

3-3: Import AIProviderName from the types barrel to prevent type-source drift.

AIProviderName is a type import at Line 3 and should come from the src/lib/types barrel rather than ../constants/enums.js.

Proposed fix
-import type { AIProviderName } from "../constants/enums.js";
+import type { AIProviderName } from "../types/index.js";

As per coding guidelines, "Code outside src/lib/types/ must import internal types from the barrel (../types/index.js or ../types), never from specific type files."

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

In `@src/lib/providers/deepseek.ts` at line 3, The import of the type
AIProviderName in src/lib/providers/deepseek.ts is coming from
../constants/enums.js which causes type-source drift; change the type-only
import to come from the types barrel (../types or ../types/index.js) instead so
AIProviderName is imported from the central types export; update the import
statement that references AIProviderName accordingly and ensure it's a type
import (import type { AIProviderName } from "../types").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/provider-integration/01-shared-changes.md`:
- Around line 245-262: The master checklist in
docs/provider-integration/01-shared-changes.md is missing a step to
update/default-model resolution from src/lib/utils/modelChoices.ts; add a bullet
instructing editors to open src/lib/utils/modelChoices.ts and ensure
DEFAULT_MODELS (and any provider-specific entries) include the new defaults used
by ProviderFactory.registerProvider (e.g., NvidiaNimModels and
AIProviderName.LM_STUDIO) so the PR’s reliance on DEFAULT_MODELS is covered by
the “touch once” guide.

In `@docs/provider-integration/04-lm-studio.md`:
- Line 330: The spec text is out of sync with the implementation: update the
sentence that says "Don't make this configurable in v1" to acknowledge that an
API key is supported and configurable; explicitly mention that while the literal
"lm-studio" token still works, the system also accepts an API key via the
environment variable LM_STUDIO_API_KEY and via credentials.lmStudio.apiKey so
readers understand both forms are supported. Ensure the revised line references
the literal token behavior and the two supported configuration points
(LM_STUDIO_API_KEY and credentials.lmStudio.apiKey).

In `@docs/provider-integration/05-llamacpp.md`:
- Around line 38-39: Update the docs to mention the optional llama.cpp API key
override supported by the provider: add a note that users can set the
environment variable LLAMACPP_API_KEY or provide credentials.llamacpp.apiKey (in
the credentials object) for reverse-proxy setups instead of the placeholder key
"llamacpp", and add the same note for the second instance referenced around the
other section (lines 69-71) so the guidance matches the provider implementation.

In `@docs/provider-integration/06-testing.md`:
- Around line 48-128: The example tests (inside runTest) are returning early on
skips but currently return undefined and assert against result?.text; update
each per-call test (DeepSeek, NVIDIA_NIM, LM Studio, llamacpp) to return null on
skip (so the suite records SKIP) and change the success checks to inspect
result?.content instead of result?.text; additionally, when detecting provider
error messages in any fetch/try-catch or response checks, normalize messages
with toLowerCase() before matching to make error detection case-insensitive
(references: runTest, NeuroLink, generate, and the result variable).

In `@docs/provider-integration/07-implementation-order.md`:
- Around line 90-104: The numbered list in
docs/provider-integration/07-implementation-order.md contains fenced code blocks
(the bash examples for steps 3, 4, and 5) that lack blank lines before and after
the triple-backtick fences; add a blank line immediately before and immediately
after each fenced block (the blocks starting with the export/ pnpm generate
examples and the vision model example) so that each fenced command is separated
from surrounding list text and satisfies markdownlint MD031.

In `@docs/provider-integration/08-feature-matrix.md`:
- Around line 54-55: Escape or wrap the Homebrew error text containing
brace-delimited fragments (e.g., the string containing {type: :arm, bits: 64}
and {type: :intel, bits: 64}) so MDX doesn't treat them as expressions; update
the documentation line that currently includes that raw error text by
surrounding the entire error message in inline code formatting (backticks) or by
escaping the braces, ensuring the literal braces remain visible and the sentence
otherwise unchanged.

In `@docs/provider-integration/10-test-results-final.md`:
- Around line 75-82: The table's totals and pass-rates (rows for DeepSeek,
NVIDIA NIM, LM Studio, llama.cpp and the header “Final Sub-test Pass Rates”)
conflict with the PR summary (380/386 overall and per-provider percentages);
reconcile by choosing the canonical source (either the PR summary or this
document), recomputing per-provider totals and pass-rates to match that source,
and updating the table values and any bold/parenthetical notes accordingly; also
add a single-line clarifier under the table stating the aggregation method used
(e.g., "Values are the PR summary totals from matrix X" or "Values are
best-of-iterations after fixes") so future readers know which dataset is
canonical.

In `@docs/provider-integration/13-code-review.md`:
- Around line 23-48: The review's "Medium-priority issues" section is now stale
because the PR already implements a mutable BaseProvider.modelName and the
getOutputReserve clamp, so update the doc to remove or mark MED-1 and MED-2 as
resolved and delete the conflicting recommendations; specifically change the
MED-1 text that advocates leaving the TS-cast workaround to instead note that
BaseProvider.modelName was converted to a mutable API (or updateModelName was
added) and remove the cast recommendation for lmStudio.ts and llamaCpp.ts, and
update MED-2 to state that getOutputReserve in contextWindows.ts has been
clamped (or that a follow-up is tracked) so the || 8192 → || 1024 workaround is
no longer necessary; also reconcile the duplicated text around lines referencing
160-165 so the section is consistent.

In `@docs/provider-integration/README.md`:
- Around line 37-47: Update the Status table so it reflects the actual
implemented work: replace the "⏳ Spec only" cells for the rows "DeepSeek
implementation", "NVIDIA NIM implementation", "LM Studio implementation",
"llama.cpp implementation", "Shared changes (types/CLI/etc)" and "Tests" with a
completed state label (e.g., "✅ Implemented" or "Done") and an optional short
note about tests passing; ensure the table formatting stays aligned and the
header "Status" remains unchanged.

In `@src/lib/adapters/providerImageAdapter.ts`:
- Around line 446-471: getVisionProviders() is returning keys from
VISION_CAPABILITIES even when the allowlist arrays are empty (e.g., deepseek),
causing messageBuilder.ts to advertise non-vision providers; update
getVisionProviders() to filter VISION_CAPABILITIES entries and only return
provider keys whose allowlist array length > 0 (i.e., skip entries where
VISION_CAPABILITIES[provider] is empty) so behavior matches supportsVision()'s
empty-allowlist guard.

In `@src/lib/providers/deepseek.ts`:
- Around line 288-294: validateConfiguration() currently re-reads env via
getDeepSeekApiKey() instead of using the instance credential resolved in the
constructor; update validateConfiguration() to check this.apiKey (and any other
instance fields set in the constructor) for presence/validity and only fallback
to getDeepSeekApiKey() if this.apiKey is unset. Specifically, inside
validateConfiguration() reference this.apiKey (and validate its
format/non-empty) rather than calling getDeepSeekApiKey() unconditionally, and
ensure the constructor-set value is what determines the returned boolean.

In `@src/lib/providers/nvidiaNim.ts`:
- Around line 299-301: The untyped declaration "let result;" weakens type
safety; explicitly type the variable using the callStream return type (e.g.,
declare result as Awaited<ReturnType<typeof callStream>> or the specific
stream/result interface used by callStream) and keep the existing try/assignment
with callStream(extraBody); this makes the variable strongly typed (and consider
using const where possible if assignment happens only once).
- Around line 262-268: The current code overwrites the caller's
options.providerOptions when adding NIM extras; change the assignment so you
merge instead of replace: if body has keys, build providerOptions by
shallow-merging existing options.providerOptions with an openai key that itself
merges existing options.providerOptions.openai with { body }, otherwise keep
options.providerOptions as-is; update the expression around providerOptions,
body, and options to use spread merging (and handle undefined safely) so
per-call overrides are preserved.

In `@src/lib/utils/modelChoices.ts`:
- Around line 284-295: TOP_MODELS_CONFIG contains sentinel entries with model:
"" for AIProviderName.LM_STUDIO and AIProviderName.LLAMACPP which leak blank
choices; update the logic that builds suggestions (e.g. getTopModelChoices() and
getPopularModelsAcrossProviders()) to filter out any entries where the model
string is empty/falsy before returning choices, or alternatively remove/replace
those sentinel entries in TOP_MODELS_CONFIG/DEFAULT_MODELS; reference
AIProviderName.LM_STUDIO, AIProviderName.LLAMACPP, TOP_MODELS_CONFIG,
DEFAULT_MODELS, getTopModelChoices(), and getPopularModelsAcrossProviders() when
applying the fix.

In `@test/continuous-test-suite-context.ts`:
- Around line 2438-2440: The test suite still falls back to 8192 for unknown
providers causing inconsistent maxTokens behavior; update the logic in the
continuous test suite so TEST_CONFIG.maxTokens is set using
PROVIDER_MAX_TOKENS[TEST_CONFIG.provider] || 1024 (matching the other file)
instead of using 8192 or a hardcoded value—locate the assignment to
TEST_CONFIG.maxTokens in continuous-test-suite.ts (the block around where 8192
is used) and replace the fallback with 1024 or the PROVIDER_MAX_TOKENS lookup to
ensure consistent behavior.

In `@test/continuous-test-suite-new-providers.ts`:
- Around line 119-130: probeLocalServer currently calls the /models endpoint
without sending Authorization, causing auth-proxied LM Studio/llama.cpp
instances to be marked unavailable; update probeLocalServer to include a Bearer
token header when present by reading the corresponding API key env vars (e.g.,
TEST_LM_STUDIO_API_KEY or LM_STUDIO_API_KEY for LM_STUDIO_URL, and
TEST_LLAMACPP_API_KEY or LLAMACPP_API_KEY for LLAMACPP_URL) and attach
Authorization: `Bearer <key>` to the probe request so secured servers are
detected as available; ensure the same change is applied to all probe calls
referenced around the existing LM_STUDIO_URL and LLAMACPP_URL checks (also lines
~134-148 and ~155-156 areas).

In `@test/continuous-test-suite-providers.ts`:
- Around line 1856-1867: The current unconditional SKIP when
generationSpans.length === 0 hides regressions for native (Pipeline B)
providers; change the branch to only SKIP for known Pipeline A providers by
checking a Pipeline A allowlist or a provider flag (e.g., add or use
isPipelineA(providerName) / PIPELINE_A_PROVIDERS) and otherwise fail/assert when
generationSpans.length === 0; update the block around generationSpans, allSpans,
and logTest so Pipeline A providers log SKIP and return null, but
native/Pipeline B providers produce a failing log/assert instead of being
skipped.
- Around line 846-876: The test named "DisableTools" is currently a
false-positive because no tool is registered on the sdk, so result.toolsUsed
will always be empty; before calling sdk.generate include a real/dummy tool
registration (or add a tool to the options returned by buildBaseSDKOptions()) so
the provider has at least one available tool to choose from, then call
sdk.generate with disableTools: true and assert result.toolsUsed is empty;
reference sdk.generate, buildBaseSDKOptions(), disableTools, and
result.toolsUsed and update the test setup to register the tool (or mock the
provider's tools) and keep existing logTest checks.

---

Outside diff comments:
In `@test/continuous-test-suite-credentials.ts`:
- Around line 545-568: The assertion message still references the old variable
name availableLower which no longer exists; update the message to use the
normalized array name availableNormalized (or otherwise format a readable list
from availableNormalized) so the string interpolation in the assert uses a valid
symbol; locate the assert block that uses matchCount and replace availableLower
with availableNormalized (or a joined form like availableNormalized.join(", "))
to avoid the compile/runtime error.

---

Duplicate comments:
In `@docs/provider-integration/00-architecture.md`:
- Around line 95-105: Add blank lines before and after the fenced TypeScript
block that documents ProviderConfigOptions so the snippet for
createMistralConfig()/createOpenAIConfig() is separated from surrounding text;
specifically, ensure there is an empty line immediately above the ```ts fence
and an empty line immediately below it to satisfy markdownlint MD031 (the block
that contains the type ProviderConfigOptions should be surrounded by those blank
lines).

In `@docs/provider-integration/03-nvidia-nim.md`:
- Around line 445-447: Add required blank lines before and after the fenced bash
block containing the grep command to satisfy MD031; locate the fenced block that
starts with ```bash and the line containing grep -rn "providerOptions"
src/lib/providers/openRouter.ts src/lib/providers/litellm.ts | head -10 and
insert one empty line immediately above the opening ```bash and one empty line
immediately below the closing ``` so the code fence is surrounded by blank
lines.

In `@docs/provider-integration/10-test-results-final.md`:
- Around line 46-50: The docs note for "Bug `#5` — Provider model name not
persisting after auto-discovery" contains an outdated claim about persisting the
discovered model using a "TS-cast escape" to write to this.modelName; update the
paragraph to remove the TS-cast escape explanation and instead state that the
discovered model is now persisted without mentioning the cast, referencing the
fields this.discoveredModel and this.modelName so reviewers know which behavior
changed.

In `@src/lib/providers/deepseek.ts`:
- Around line 177-183: The code currently forces providerOptions.openai.thinking
= { type: "enabled" } whenever isReasoner is false, which makes DeepSeek always
use thinking mode; change this to be opt-in by only setting
providerOptions.openai.thinking when the call explicitly requests thinking
(e.g., via a passed-in flag or in the caller's options) instead of defaulting
for non-reasoner flows; update the logic around providerOptions and isReasoner
in the DeepSeek handler (deepseek-chat entry) so that if no explicit thinking
request is present you leave providerOptions.openai.thinking undefined, and
preserve existing behavior when thinking is explicitly enabled.

In `@test/run-provider-matrix.sh`:
- Around line 143-163: The matrix script iterates a hardcoded list "for suite in
$SUITES context memory;" which prints rows for "context" and "memory" even
though the runner never adds them to SUITES; update the loop to either remove
the hardcoded "context memory" (so only suites in SUITES are printed) or ensure
SUITES is augmented with "context" and "memory" before the loop (e.g., append
them to the SUITES variable), and keep the existing file-checking/case logic
(look for the loop that references SUITES and the subsequent
f="$RESULTS/$p/${suite}.summary" handling to locate where to change the
iteration).

---

Nitpick comments:
In `@src/cli/factories/commandFactory.ts`:
- Around line 60-87: The provider choice array in commandFactory.ts is
duplicated across CLI builders and completion scripts; extract that array into a
shared constant (e.g., PROVIDER_CHOICES) exported from a single module and
replace the inline choices array in commandFactory.ts (the provider: { choices:
[...] } block) and other callers (generate/setup builders and the shell
completion generator) to import and use PROVIDER_CHOICES so all CLI builders and
completion code reference the same source of truth.

In `@src/lib/providers/deepseek.ts`:
- Line 3: The import of the type AIProviderName in src/lib/providers/deepseek.ts
is coming from ../constants/enums.js which causes type-source drift; change the
type-only import to come from the types barrel (../types or ../types/index.js)
instead so AIProviderName is imported from the central types export; update the
import statement that references AIProviderName accordingly and ensure it's a
type import (import type { AIProviderName } from "../types").

In `@src/lib/utils/providerConfig.ts`:
- Around line 425-495: The new helper functions createDeepSeekConfig,
createNvidiaNimConfig, createLmStudioConfig, and createLlamaCppConfig are dead
code because providerRegistry.ts still hardcodes those provider defaults; either
wire these helpers into the registry (replace the hardcoded DeepSeek/NVIDIA
NIM/LM Studio/llama.cpp entries in providerRegistry.ts to call the respective
functions) or remove the helper functions to avoid drifting setup text; update
any setup/guidance rendering to consume the ProviderConfigOptions returned by
these functions so the registry and UI stay consistent.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d88323cb-ca4d-4003-889e-4c53ae87d11b

📥 Commits

Reviewing files that changed from the base of the PR and between 971bce7 and 3320248.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (49)
  • .env.example
  • docs/provider-integration/00-architecture.md
  • docs/provider-integration/01-shared-changes.md
  • docs/provider-integration/02-deepseek.md
  • docs/provider-integration/03-nvidia-nim.md
  • docs/provider-integration/04-lm-studio.md
  • docs/provider-integration/05-llamacpp.md
  • docs/provider-integration/06-testing.md
  • docs/provider-integration/07-implementation-order.md
  • docs/provider-integration/08-feature-matrix.md
  • docs/provider-integration/09-test-suite-spec.md
  • docs/provider-integration/10-test-results-final.md
  • docs/provider-integration/11-test-failure-investigation.md
  • docs/provider-integration/12-pr-analysis.md
  • docs/provider-integration/13-code-review.md
  • docs/provider-integration/README.md
  • package.json
  • src/cli/factories/commandFactory.ts
  • src/lib/adapters/providerImageAdapter.ts
  • src/lib/constants/contextWindows.ts
  • src/lib/constants/enums.ts
  • src/lib/core/baseProvider.ts
  • src/lib/factories/providerRegistry.ts
  • src/lib/providers/deepseek.ts
  • src/lib/providers/index.ts
  • src/lib/providers/llamaCpp.ts
  • src/lib/providers/lmStudio.ts
  • src/lib/providers/nvidiaNim.ts
  • src/lib/types/providers.ts
  • src/lib/utils/modelChoices.ts
  • src/lib/utils/pricing.ts
  • src/lib/utils/providerConfig.ts
  • test/continuous-test-suite-client.ts
  • test/continuous-test-suite-context.ts
  • test/continuous-test-suite-credentials.ts
  • test/continuous-test-suite-evaluation-scoring.ts
  • test/continuous-test-suite-evaluation.ts
  • test/continuous-test-suite-mcp-http.ts
  • test/continuous-test-suite-mcp.ts
  • test/continuous-test-suite-media-gen.ts
  • test/continuous-test-suite-memory.ts
  • test/continuous-test-suite-new-providers.ts
  • test/continuous-test-suite-observability.ts
  • test/continuous-test-suite-ppt.ts
  • test/continuous-test-suite-providers.ts
  • test/continuous-test-suite-session-memory-bugs.ts
  • test/continuous-test-suite-tts.ts
  • test/continuous-test-suite-workflow.ts
  • test/run-provider-matrix.sh

Comment thread docs/provider-integration/01-shared-changes.md
Comment thread docs/provider-integration/04-lm-studio.md Outdated
Comment thread docs/provider-integration/05-llamacpp.md Outdated
Comment on lines +48 to +128
```ts
// DeepSeek credential override
await runTest("deepseek per-call apiKey override", async () => {
if (!process.env.DEEPSEEK_API_KEY) {
console.log(
` ${colors.yellow}[SKIP]${colors.reset} DEEPSEEK_API_KEY not set`,
);
return;
}
const nl = new NeuroLink();
const result = await nl.generate({
input: { text: "Reply with the word PONG and nothing else." },
provider: "deepseek",
credentials: { deepseek: { apiKey: process.env.DEEPSEEK_API_KEY } },
maxTokens: 16,
});
assert(typeof result?.text === "string", "should return text");
});

// NVIDIA NIM credential override
await runTest("nvidia-nim per-call apiKey override", async () => {
if (!process.env.NVIDIA_NIM_API_KEY) {
console.log(
` ${colors.yellow}[SKIP]${colors.reset} NVIDIA_NIM_API_KEY not set`,
);
return;
}
const nl = new NeuroLink();
const result = await nl.generate({
input: { text: "Reply with PONG only." },
provider: "nvidia-nim",
credentials: { nvidiaNim: { apiKey: process.env.NVIDIA_NIM_API_KEY } },
maxTokens: 16,
});
assert(typeof result?.text === "string", "should return text");
});

// LM Studio credential override (baseURL only)
await runTest("lm-studio per-call baseURL override", async () => {
const url = process.env.LM_STUDIO_BASE_URL || "http://localhost:1234/v1";
// Skip if server not reachable
try {
const r = await fetch(`${url.replace(/\/$/, "")}/models`);
if (!r.ok) throw new Error("not ok");
} catch {
console.log(
` ${colors.yellow}[SKIP]${colors.reset} LM Studio not running at ${url}`,
);
return;
}
const nl = new NeuroLink();
const result = await nl.generate({
input: { text: "Reply with PONG only." },
provider: "lm-studio",
credentials: { lmStudio: { baseURL: url } },
maxTokens: 16,
});
assert(typeof result?.text === "string", "should return text");
});

// llama.cpp credential override
await runTest("llamacpp per-call baseURL override", async () => {
const url = process.env.LLAMACPP_BASE_URL || "http://localhost:8080/v1";
try {
const r = await fetch(`${url.replace(/\/$/, "")}/models`);
if (!r.ok) throw new Error("not ok");
} catch {
console.log(
` ${colors.yellow}[SKIP]${colors.reset} llama-server not running at ${url}`,
);
return;
}
const nl = new NeuroLink();
const result = await nl.generate({
input: { text: "Reply with PONG only." },
provider: "llamacpp",
credentials: { llamacpp: { baseURL: url } },
maxTokens: 16,
});
assert(typeof result?.text === "string", "should return text");
});

ghost Apr 29, 2026

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

Align these example tests with the actual suite contract.

The skip paths should return null, and the success checks should inspect result?.content rather than result?.text. As written, these snippets encourage false-skip handling and assertions against the wrong field.

Based on learnings, in continuous test suites, use case-insensitive provider error detection by normalizing messages with toLowerCase() and represent skipped scenarios by returning null (with SKIP status) to avoid false passes.

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

In `@docs/provider-integration/06-testing.md` around lines 48 - 128, The example
tests (inside runTest) are returning early on skips but currently return
undefined and assert against result?.text; update each per-call test (DeepSeek,
NVIDIA_NIM, LM Studio, llamacpp) to return null on skip (so the suite records
SKIP) and change the success checks to inspect result?.content instead of
result?.text; additionally, when detecting provider error messages in any
fetch/try-catch or response checks, normalize messages with toLowerCase() before
matching to make error detection case-insensitive (references: runTest,
NeuroLink, generate, and the result variable).

Comment thread docs/provider-integration/07-implementation-order.md
Comment thread src/lib/providers/nvidiaNim.ts Outdated
Comment thread test/continuous-test-suite-context.ts
Comment thread test/continuous-test-suite-new-providers.ts
Comment thread test/continuous-test-suite-providers.ts Outdated
Comment thread test/continuous-test-suite-providers.ts Outdated
@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

Round 2 (HEAD a230541) — addressed 17 of 18 actionable comments from the third CodeRabbit pass:

Critical

  • docs/provider-integration/08-feature-matrix.md: wrapped the Homebrew error string ({type: :arm, …}) in code fencing so MDX stops parsing the braces as JSX expressions. Resolves the docs-validation failure.

Major / correctness

  • src/lib/providers/deepseek.ts + nvidiaNim.ts: validateConfiguration() now checks this.apiKey (resolved in the constructor from credentials → env → fallback) instead of re-reading env via getDeepSeekApiKey() / getNimApiKey(). Per-call credentials are no longer ignored.
  • src/lib/providers/nvidiaNim.ts: providerOptions no longer overwrites caller's openai.body — NIM extras are now merged with any per-call overrides via spread.
  • src/lib/providers/nvidiaNim.ts: let result; → let result: Awaited<ReturnType<typeof callStream>>; (Biome noImplicitAnyLet fix).
  • test/continuous-test-suite.ts:5099: || 8192 → || 1024 to match the per-suite fix already applied to other test entrypoints. Closes the 'Budget: 0' regression for unmapped providers in the orchestrator path.
  • test/continuous-test-suite-new-providers.ts: probeLocalServer now accepts an optional Bearer key sourced from LM_STUDIO_API_KEY / LLAMACPP_API_KEY (or their TEST_* overrides). Auth-proxied LM Studio / llama-server setups are no longer silently skipped.
  • test/continuous-test-suite-providers.ts (DisableTools): registers a real dummy_tool on a fresh NeuroLink before the assertion so toolsUsed.length === 0 actually proves the provider honored disableTools (was previously vacuously true).
  • test/continuous-test-suite-providers.ts (Observability Spans): replaced unconditional SKIP-on-zero-spans with a Pipeline-A allowlist. Native (Pipeline B) providers that fail to emit model.generation now FAIL instead of silently passing.

Minor / consistency / docs

  • src/lib/adapters/providerImageAdapter.ts: getVisionProviders() now filters out providers whose allowlist is empty (e.g. deepseek). Consumers no longer see deepseek in the advertised vision-provider list.
  • src/lib/utils/modelChoices.ts: getTopModelChoices() and getPopularModelsAcrossProviders() skip entries with an empty model string (the LM Studio / llama.cpp auto-discovery sentinels). CLI choice lists no longer leak blank rows.
  • docs/provider-integration/README.md: Status table flipped from ⏳ Spec only to ✅ Implemented with file paths.
  • docs/provider-integration/13-code-review.md: MED-1 (refreshHandlersForModel) and MED-2 (getOutputReserve clamp) marked as resolved-in-PR. Final recommendation now reads Approve.
  • docs/provider-integration/10-test-results-final.md: aggregation method clarifier added — pass-rates here are best-of-iterations across runs, not the single PR-summary 380/386 figure.
  • docs/provider-integration/04-lm-studio.md: edge-case 3 now mentions LM_STUDIO_API_KEY / credentials.lmStudio.apiKey for proxied deployments.
  • docs/provider-integration/05-llamacpp.md: same for LLAMACPP_API_KEY / credentials.llamacpp.apiKey.
  • docs/provider-integration/06-testing.md: example credential-override tests now return null on skip and assert result?.content instead of result?.text.
  • docs/provider-integration/01-shared-changes.md: added §8a covering src/lib/utils/modelChoices.ts (TOP_MODELS_CONFIG + DEFAULT_MODELS) so the "touch once" checklist no longer omits the file the new providers depend on for default-model resolution.

Skipped (not safe to apply)

  • markdownlint MD031 in 07-implementation-order.md L91-104 — the fenced blocks are inside a numbered list. Adding the suggested blank lines breaks list-continuation indentation. Same call as round 1.

Verification: pnpm run check 0 errors, pnpm run lint 0 errors (18 pre-existing warnings unchanged), pnpm run format applied.

Single-commit policy followed (--amend + --force-with-lease).

@coderabbitai full review please

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown

@murdore — thank you for the detailed Round 2 summary. The fixes are well-targeted and thorough:

  • validateConfiguration credential fix (DeepSeek + NIM): checking this.apiKey (constructor-resolved) instead of re-reading env is the correct approach — per-call credentials passed through the constructor chain will now actually be validated.
  • NIM providerOptions spread: merging instead of overwriting ensures per-call openai.body overrides are preserved — a subtle but important correctness fix.
  • let result explicit type: clean Biome compliance, and the inferred type is exactly right for the retry path.
  • Orchestrator || 1024 fix: closes the last 'Budget: 0' gap — the per-suite fixes were already in place but the orchestrator path was still holding the old 8192 default.
  • probeLocalServer Bearer key forwarding: auth-proxied local server setups would have silently skipped previously — this closes a real usability gap.
  • DisableTools dummy_tool registration: the previous assertion was vacuously true (no tools registered → toolsUsed.length === 0 is trivially satisfied regardless of disableTools). The fix makes the test actually meaningful.
  • Observability Spans Pipeline-A allowlist: more principled than unconditional SKIP — native (Pipeline B) providers that genuinely fail to emit model.generation spans will now surface as failures rather than passing silently.
  • getVisionProviders() empty-allowlist filter + model-choice sentinel skip: both are good defensive hygiene for the CLI surface.

The MD031 skip in 07-implementation-order.md remains the correct call for the same reason as rounds 1 and 2.

Kicking off a fresh full review against a230541c now.


🧠 Learnings used
Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-17T04:30:25.521Z
Learning: In neurolink (src/lib/neurolink.ts), the `pipelineAHandled: true` flag on `generation:end` events is set ONLY by `finalizeGenerateRequestResult` (the SDK-level wrapper). Native providers (amazonBedrock.ts, ollama.ts, googleAiStudio.ts, googleVertex.ts) emit their own `generation:end` events WITHOUT this flag. The `initializeMetricsListeners()` early-return guard `if (data.pipelineAHandled) return` correctly prevents duplicates: it skips the SDK-level event while allowing native provider events through to Pipeline B.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 174
File: todos/refactor/03-providers-module.md:3-6
Timestamp: 2025-09-28T21:08:19.655Z
Learning: RajuSudhar mentioned that remaining interfaces in the SageMaker module (30+ interfaces across 5 files) will be resolved in a different PR, but no specific PR was found. The providers module refactor was marked COMPLETED prematurely while significant SageMaker interface conversion work remains pending.

Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-24T03:50:12.966Z
Learning: Applies to src/lib/types/providers.ts : Add new provider names to `AIProviderName` enum in `src/lib/types/providers.ts`
✅ Actions performed

Full review triggered.

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

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

⚠️ Outside diff range comments (2)
src/lib/proxy/proxyFetch.ts (1)

766-785: ⚠️ Potential issue | 🟠 Major

Redaction gap in maskProxyUrl can leak signed query credentials.

The updated docs say signed query params must not be logged, but maskProxyUrl currently only masks username/password and returns query/hash unchanged.

🔧 Proposed fix
 export function maskProxyUrl(url: string | null | undefined): string | null {
   if (!url) {
     return null;
   }
   try {
     const u = new URL(url);
     if (u.username || u.password) {
       u.username = "***";
       u.password = "***";
     }
+    // Signed URLs may carry credentials in query/hash; never log them raw.
+    if (u.search) {
+      u.search = "?<redacted>";
+    }
+    u.hash = "";
     return u.toString();
   } catch {
     return "[invalid-url]";
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/proxy/proxyFetch.ts` around lines 766 - 785, maskProxyUrl currently
only redacts URL username/password but leaves query parameters and the fragment
intact, which can leak signed query credentials; update maskProxyUrl to iterate
u.searchParams and replace every parameter value with a fixed redaction token
(e.g. "***") while keeping parameter names, and also redact u.hash (fragment) by
replacing it with something like "#***" when present, preserving the try/catch
and existing username/password masking in the maskProxyUrl function so the
function returns a safe, masked URL string or the same error result for invalid
URLs.
test/continuous-test-suite-credentials.ts (1)

545-568: ⚠️ Potential issue | 🔴 Critical

availableLower is undefined after this refactor.

The new code renamed the normalized provider list to availableNormalized, but the assertion message still references availableLower. That leaves this test file with a broken identifier and prevents the suite from type-checking cleanly.

Suggested fix
     assert(
       matchCount >= 5,
       `Expected at least 5 credential keys to match registered providers. ` +
-        `Got ${matchCount} matches. Available providers: ${availableLower.join(", ")}`,
+        `Got ${matchCount} matches. Available providers: ${availableNormalized.join(", ")}`,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-credentials.ts` around lines 545 - 568, The
assertion message references a removed identifier availableLower causing a
runtime/type error; update the assert call to use the existing normalized list
(availableNormalized) or the original available array — e.g., replace
availableLower.join(", ") with availableNormalized.join(", ") (or
available.join(", ")) so the message uses a defined symbol; check the assert
near matchCount and ensure availableLower is not referenced elsewhere.
♻️ Duplicate comments (13)
docs/provider-integration/07-implementation-order.md (1)

91-104: ⚠️ Potential issue | 🟡 Minor

Fenced command blocks still violate markdownlint MD031.

The fenced blocks in this list need blank lines before/after each fence to satisfy linting in this section.

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

In `@docs/provider-integration/07-implementation-order.md` around lines 91 - 104,
The fenced code blocks in this section violate markdownlint MD031; add a blank
line before and after each triple-backtick fence so each fenced block is
separated from surrounding list items and text—specifically update the export
command block and the two bash examples under "4. Validate retry-on-400" and "5.
Validate vision model" (the ```bash fences) to have an empty line above the
opening ``` and an empty line below the closing ``` so the linter no longer
flags MD031.
test/run-provider-matrix.sh (1)

146-166: ⚠️ Potential issue | 🟡 Minor

Matrix rows include suites this script never executes.

At Line 146, the matrix loop appends context and memory, but the run loop at Line 76 only executes $SUITES. This produces persistent — cells and makes the matrix look partially missing instead of “not executed.”

Suggested fix
-  for suite in $SUITES context memory; do
+  for suite in $SUITES; do
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/run-provider-matrix.sh` around lines 146 - 166, The matrix printing loop
currently iterates "for suite in $SUITES context memory;" but the runner only
executes "$SUITES", causing persistent "—" cells for suites never run; change
the loop to only iterate the actual suites the runner uses (replace "for suite
in $SUITES context memory;" with "for suite in $SUITES; do") or, if you intended
to include "context" and "memory" in runs, add them to the SUITES variable where
tests are launched so both the run loop and the summary loop use the same SUITES
set (references: SUITES, the for-suite loop, and the summary file lookup
f="$RESULTS/$p/${suite}.summary").
docs/provider-integration/05-llamacpp.md (2)

85-97: ⚠️ Potential issue | 🟠 Major

Declare and initialize requestedModelName in the sample.

getAISDKModel() branches on this.requestedModelName, but the class never defines or assigns it. Copied as-is, this example does not type-check and the discovery-vs-explicit logic is incomplete.

Based on learnings: In juspay/neurolink PR #997, lmStudio.ts and llamaCpp.ts use a requestedModelName (constructor-captured, never mutated) to distinguish explicit model selection from auto-discovery. this.modelName is updated by refreshHandlersForModel() on discovery success/fallback, but the discovery-vs-explicit branch always reads requestedModelName so that a FALLBACK_MODEL assignment never short-circuits future /v1/models retries.

Also applies to: 152-159

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

In `@docs/provider-integration/05-llamacpp.md` around lines 85 - 97, The sample
class LlamaCppProvider uses requestedModelName in getAISDKModel() but never
declares or assigns it; update the class to declare a private readonly
requestedModelName?: string and initialize it from the constructor parameter
(modelName) so discovery vs explicit selection works correctly (keep
this.requestedModelName immutable while this.modelName can be updated by
refreshHandlersForModel()); ensure getAISDKModel() reads this.requestedModelName
for branching and that refreshHandlersForModel() continues to update
this.modelName when discovery succeeds or falls back.

109-116: ⚠️ Potential issue | 🟠 Major

Keep auth/proxy validation consistent with the advertised API-key override.

The doc says LLAMACPP_API_KEY / credentials.llamacpp.apiKey are supported, but this sample hardcodes the placeholder key and validateConfiguration() uses plain fetch() without auth or timeout. Reverse-proxy deployments can pass normal requests yet still be reported as unhealthy here.

Also applies to: 125-137, 322-330

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

In `@docs/provider-integration/05-llamacpp.md` around lines 109 - 116, The sample
currently hardcodes LLAMACPP_PLACEHOLDER_KEY and calls validateConfiguration
using the default fetch, so auth/proxy checks are inconsistent with the docs;
change initialization of this.apiKey to prefer credentials.llamacpp.apiKey or
process.env.LLAMACPP_API_KEY instead of LLAMACPP_PLACEHOLDER_KEY, and ensure
createOpenAI is created with the same proxy-aware fetch (createProxyFetch()) and
that validateConfiguration is invoked with that proxy fetch and the api key/auth
headers and a sensible timeout; update usage points referenced (this.baseURL,
this.apiKey, LLAMACPP_PLACEHOLDER_KEY, createOpenAI, validateConfiguration,
createProxyFetch, credentials.llamacpp.apiKey, LLAMACPP_API_KEY) so the health
check uses the real auth+proxy configuration.
docs/provider-integration/03-nvidia-nim.md (2)

79-85: ⚠️ Potential issue | 🟡 Minor

Import the shared NvidiaNimExtraBody type in this sample.

The snippet uses NvidiaNimExtraBody, but the import list omits it, and the surrounding note still says NvidiaNvidiaNimExtraBody. Copied as-is, this example will not type-check.

Also applies to: 103-127

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

In `@docs/provider-integration/03-nvidia-nim.md` around lines 79 - 85, The import
list in the sample is missing the shared type NvidiaNimExtraBody and the
surrounding note contains a duplicated typo "NvidiaNvidiaNimExtraBody"; update
the import statement to include NvidiaNimExtraBody alongside UnknownRecord,
NeurolinkCredentials, StreamOptions, StreamResult, and ValidationSchema, and
correct the note text to reference NvidiaNimExtraBody exactly (also apply the
same fixes in the other affected block around lines 103-127).

489-493: ⚠️ Potential issue | 🟡 Minor

Add blank lines around the fenced verification block.

This still trips MD031 in docs CI.

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

In `@docs/provider-integration/03-nvidia-nim.md` around lines 489 - 493, There is
a fenced verification code block that lacks surrounding blank lines (tripping
MD031); add a single blank line immediately before the opening ``` and a single
blank line immediately after the closing ``` for the verification block so the
markdown linter passes.
docs/provider-integration/12-pr-analysis.md (1)

7-12: ⚠️ Potential issue | 🟡 Minor

The markdown-file count still doesn't match Section F.

The overview says 13 new Markdown docs, but the file list below enumerates 15 entries under docs/provider-integration/.

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

In `@docs/provider-integration/12-pr-analysis.md` around lines 7 - 12, The summary
line in docs/provider-integration/12-pr-analysis.md ("13 new Markdown docs") is
inconsistent with the enumerated list (15 entries); open that file and update
the summary count to match the actual list (change "13 new Markdown docs" to "15
new Markdown docs") or alternatively remove/merge the two extra entries in the
docs/provider-integration/ list so both the bullet and the list are consistent;
make the edit in the bullet section shown in the diff so the document and
enumeration align.
docs/provider-integration/06-testing.md (1)

67-128: ⚠️ Potential issue | 🟡 Minor

Align these examples with the current runner contract.

Several snippets here still assert on result?.text, and the LM Studio skip path uses a bare return. The current suites record success from result?.content and require null for SKIP, so copying these examples will reproduce the old contract drift.

Based on learnings: In continuous test suites, use case-insensitive provider error detection by normalizing messages with toLowerCase() and represent skipped scenarios by returning null (with SKIP status) to avoid false passes.

Also applies to: 143-159

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

In `@docs/provider-integration/06-testing.md` around lines 67 - 128, Update the
test snippets to match the runner contract: change assertions that check
result?.text to assert on result?.content (e.g., in the "nvidia-nim" and
"lm-studio" blocks replace result?.text with result?.content), ensure all SKIP
paths return null (replace bare return in the LM Studio skip with return null),
and when detecting provider-specific error messages normalize the string with
toLowerCase() before matching to make detection case-insensitive; locate these
changes around the runTest calls and the NeuroLink.generate usage (provider
"nvidia-nim", "lm-studio", "llamacpp") and update the assertions and skip
returns accordingly.
docs/provider-integration/09-test-suite-spec.md (1)

165-199: ⚠️ Potential issue | 🟡 Minor

Clean up the stale local-probe sample.

This snippet still has the old HAS_LM_STUDIO / HAS_LLAMACPP booleans, then switches to LM_STUDIO_PROBE / LLAMACPP_PROBE and LM_STUDIO_URL / LLAMACPP_URL. Copied as-is, it no longer matches the real suite and won't even read consistently.

📝 Suggested doc sync
-const HAS_LM_STUDIO = await probeLocalServer(
-  process.env.TEST_LM_STUDIO_BASE_URL ??
-    process.env.LM_STUDIO_BASE_URL ??
-    "http://localhost:1234/v1",
-);
-const HAS_LLAMACPP = await probeLocalServer(
-  process.env.TEST_LLAMACPP_BASE_URL ??
-    process.env.LLAMACPP_BASE_URL ??
-    "http://localhost:8080/v1",
-);
+const LM_STUDIO_URL =
+  process.env.TEST_LM_STUDIO_BASE_URL ??
+  process.env.LM_STUDIO_BASE_URL ??
+  "http://localhost:1234/v1";
+const LLAMACPP_URL =
+  process.env.TEST_LLAMACPP_BASE_URL ??
+  process.env.LLAMACPP_BASE_URL ??
+  "http://localhost:8080/v1";
 
 const LM_STUDIO_KEY =
   process.env.TEST_LM_STUDIO_API_KEY ?? process.env.LM_STUDIO_API_KEY ?? "";
 const LLAMACPP_KEY =
   process.env.TEST_LLAMACPP_API_KEY ?? process.env.LLAMACPP_API_KEY ?? "";
 
 const LM_STUDIO_PROBE = await probeLocalServer(LM_STUDIO_URL, LM_STUDIO_KEY);
 const LLAMACPP_PROBE = await probeLocalServer(LLAMACPP_URL, LLAMACPP_KEY);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/provider-integration/09-test-suite-spec.md` around lines 165 - 199, The
snippet mixes old booleans (HAS_LM_STUDIO, HAS_LLAMACPP) with the newer probe
objects and URL vars (LM_STUDIO_PROBE, LLAMACPP_PROBE, LM_STUDIO_URL,
LLAMACPP_URL) which is inconsistent; remove the stale HAS_* probes and instead
call probeLocalServer using the correct LM_STUDIO_URL and LLAMACPP_URL (and
keys) once, then populate PROVIDERS_UNDER_TEST using LM_STUDIO_PROBE.available /
.loadedModel and LLAMACPP_PROBE.available / .loadedModel so the provider entries
are consistent (update any references to HAS_LM_STUDIO/HAS_LLAMACPP to use the
corresponding PROBE objects).
docs/provider-integration/04-lm-studio.md (1)

87-99: ⚠️ Potential issue | 🟡 Minor

Declare the requestedModelName that this example now depends on.

The discovery branch reads this.requestedModelName, but the class snippet never defines or initializes that field. Copied as-is, the sample won't compile and it doesn't actually document the fallback-poisoning fix.

Based on learnings: In juspay/neurolink PR #997, lmStudio.ts and llamaCpp.ts use a requestedModelName (constructor-captured, never mutated) to distinguish explicit model selection from auto-discovery. this.modelName is updated by refreshHandlersForModel() on discovery success/fallback, but the discovery-vs-explicit branch always reads requestedModelName so that a FALLBACK_MODEL assignment never short-circuits future /v1/models retries.

Also applies to: 157-163

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

In `@docs/provider-integration/04-lm-studio.md` around lines 87 - 99, The class
LMStudioProvider is missing the requestedModelName field used by the discovery
branch; add a private readonly requestedModelName (or similar) to the class and
initialize it in the constructor from the incoming modelName parameter so that
this.requestedModelName is defined and immutable; keep using this.modelName
(updated by refreshHandlersForModel()) for the live/active model while leaving
this.requestedModelName as the original explicit request so the discovery logic
(and FALLBACK_MODEL / /v1/models retry path) can correctly detect explicit vs
auto-selected models.
src/lib/providers/lmStudio.ts (1)

213-233: ⚠️ Potential issue | 🟠 Major

Resolve LM Studio's model once per stream request.

When autodiscovery misses, this request probes /v1/models before the span and then probes again in executeStreamInner(). That doubles the 5s miss path and can stamp the span with local-model while streamText() uses a later-discovered model. Resolve once in executeStream() and pass the resolved model/name through the inner path.

Suggested fix
   protected async executeStream(
     options: StreamOptions,
     _analysisSchema?: ValidationSchema,
   ): Promise<StreamResult> {
-    await this.getAISDKModel();
+    const model = await this.getAISDKModelWithMiddleware(options);
+    const resolvedModelName =
+      this.modelName || this.discoveredModel || FALLBACK_MODEL;
+
     return withClientSpan(
       {
         name: "neurolink.provider.stream",
         tracer: tracers.provider,
         attributes: {
           [ATTR.GEN_AI_SYSTEM]: "lm-studio",
-          [ATTR.GEN_AI_MODEL]:
-            this.modelName || this.discoveredModel || FALLBACK_MODEL,
+          [ATTR.GEN_AI_MODEL]: resolvedModelName,
           [ATTR.GEN_AI_OPERATION]: "stream",
           [ATTR.NL_STREAM_MODE]: true,
         },
       },
-      async () => this.executeStreamInner(options),
+      async () => this.executeStreamInner(options, model, resolvedModelName),
     );
   }

   private async executeStreamInner(
     options: StreamOptions,
+    model: LanguageModel,
+    resolvedModelName: string,
   ): Promise<StreamResult> {
@@
-      const model = await this.getAISDKModelWithMiddleware(options);
       const messages = await this.buildMessagesForStream(options);
@@
       const analyticsPromise = streamAnalyticsCollector.createAnalytics(
         this.providerName,
-        this.modelName || this.discoveredModel || FALLBACK_MODEL,
+        resolvedModelName,
         toAnalyticsStreamResult(result),
@@
       return {
         stream: transformedStream,
         provider: this.providerName,
-        model: this.modelName || this.discoveredModel || FALLBACK_MODEL,
+        model: resolvedModelName,
         analytics: analyticsPromise,
         metadata: { startTime, streamId: `lmstudio-${Date.now()}` },
       };

Also applies to: 256-321

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

In `@src/lib/providers/lmStudio.ts` around lines 213 - 233, The stream path
currently calls getAISDKModel() before the span but then calls
executeStreamInner() which may re-run discovery, doubling latency and causing
inconsistent model attributes; resolve the model once in executeStream by
calling getAISDKModel(), compute the effective model identifier (using
this.modelName || this.discoveredModel || FALLBACK_MODEL) and pass that resolved
model/name into executeStreamInner (update executeStreamInner signature or
provide an explicit parameter such as resolvedModelName) so the inner logic and
the span use the same model; apply the same change pattern to the analogous
execute()/executeInner pair referenced around lines 256-321 to prevent duplicate
discovery there as well.
src/lib/providers/llamaCpp.ts (1)

205-225: ⚠️ Potential issue | 🟠 Major

Resolve llama.cpp's model once per stream request.

This has the same double-discovery pattern as LM Studio: a miss before the span is retried again inside executeStreamInner(). On an unavailable server that adds another 5s probe, and if the server comes up between the two calls, telemetry/returned metadata can describe a different model than the one used for streamText().

Suggested fix
   protected async executeStream(
     options: StreamOptions,
     _analysisSchema?: ValidationSchema,
   ): Promise<StreamResult> {
-    await this.getAISDKModel();
+    const model = await this.getAISDKModelWithMiddleware(options);
+    const resolvedModelName =
+      this.modelName || this.discoveredModel || FALLBACK_MODEL;
+
     return withClientSpan(
       {
         name: "neurolink.provider.stream",
         tracer: tracers.provider,
         attributes: {
           [ATTR.GEN_AI_SYSTEM]: "llamacpp",
-          [ATTR.GEN_AI_MODEL]:
-            this.modelName || this.discoveredModel || FALLBACK_MODEL,
+          [ATTR.GEN_AI_MODEL]: resolvedModelName,
           [ATTR.GEN_AI_OPERATION]: "stream",
           [ATTR.NL_STREAM_MODE]: true,
         },
       },
-      async () => this.executeStreamInner(options),
+      async () =>
+        this.executeStreamInner(options, model, resolvedModelName),
     );
   }

   private async executeStreamInner(
     options: StreamOptions,
+    model: LanguageModel,
+    resolvedModelName: string,
   ): Promise<StreamResult> {
@@
-      const model = await this.getAISDKModelWithMiddleware(options);
       const messages = await this.buildMessagesForStream(options);
@@
       const analyticsPromise = streamAnalyticsCollector.createAnalytics(
         this.providerName,
-        this.modelName || this.discoveredModel || FALLBACK_MODEL,
+        resolvedModelName,
         toAnalyticsStreamResult(result),
@@
       return {
         stream: transformedStream,
         provider: this.providerName,
-        model: this.modelName || this.discoveredModel || FALLBACK_MODEL,
+        model: resolvedModelName,
         analytics: analyticsPromise,
         metadata: { startTime, streamId: `llamacpp-${Date.now()}` },
       };

Also applies to: 248-311

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

In `@src/lib/providers/llamaCpp.ts` around lines 205 - 225, The executeStream path
currently calls getAISDKModel() before opening the span but executeStreamInner()
also triggers discovery, causing double-discovery and inconsistent telemetry;
fix by resolving the model exactly once per stream request: call await
this.getAISDKModel() in executeStream (as shown), ensure this.discoveredModel /
this.modelName is set and then remove or guard any call to getAISDKModel()
inside executeStreamInner (and any helper it calls) so executeStreamInner uses
the already-resolved model info for telemetry/operations (update references in
executeStreamInner, streamText, or other inner helpers to read
this.discoveredModel rather than re-invoking getAISDKModel()).
src/lib/providers/deepseek.ts (1)

166-185: ⚠️ Potential issue | 🟠 Major

Don't force thinking on every deepseek-chat request.

This still enables openai.thinking for every non-reasoner call, so plain deepseek-chat requests are no longer opt-in. It also replaces any request-level providerOptions instead of merging them. Please gate this on the request’s thinking flag and merge into the caller’s existing OpenAI options.

Suggested fix
-        providerOptions: !isReasoner
-          ? {
-              openai: {
-                thinking: { type: "enabled" },
-              },
-            }
-          : undefined,
+        providerOptions: (() => {
+          const callerBase =
+            ((options as unknown as Record<string, unknown>).providerOptions as
+              Record<string, unknown> | undefined) ?? {};
+          const callerOpenai =
+            (callerBase.openai as Record<string, unknown> | undefined) ?? {};
+          const enableThinking =
+            !isReasoner && !!options.thinkingConfig?.enabled;
+
+          if (!enableThinking && Object.keys(callerBase).length === 0) {
+            return undefined;
+          }
+
+          return {
+            ...callerBase,
+            openai: {
+              ...callerOpenai,
+              ...(enableThinking
+                ? { thinking: { type: "enabled" } }
+                : {}),
+            },
+          };
+        })(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/providers/deepseek.ts` around lines 166 - 185, The current streamText
call unconditionally sets providerOptions.openai.thinking for all non-reasoner
calls and overwrites caller providerOptions; change this to only enable thinking
when the request explicitly asks for it (e.g., options.thinking) and merge the
thinking flag into any existing options.providerOptions instead of replacing
them. Specifically, before calling streamText (in the same scope as isReasoner
and options), build a mergedProviderOptions that starts from
options.providerOptions (without mutating it) and, if !isReasoner &&
options.thinking, sets mergedProviderOptions.openai = {
...(options.providerOptions?.openai), thinking: { type: "enabled" } }; then pass
mergedProviderOptions to streamText’s providerOptions parameter. Ensure you
reference the streamText call and the isReasoner variable when applying this
change.
🧹 Nitpick comments (2)
test/continuous-test-suite-observability.ts (1)

63-67: Thread TEST_CONFIG.maxTokens into buildGenerateOptions() or drop this map.

These new provider caps never affect the suite right now because buildGenerateOptions() still hardcodes maxTokens: 50. That makes the added map/fallback look active when the tests are still running with a fixed budget.

♻️ Suggested cleanup
 function buildGenerateOptions(
   extraOpts: Record<string, unknown> = {},
 ): GenerateOptions {
   const opts: Record<string, unknown> = {
     input: { text: 'Say "hello" and nothing else' },
     provider: TEST_CONFIG.provider,
-    maxTokens: 50,
+    maxTokens: TEST_CONFIG.maxTokens ?? 50,
     disableTools: true,
     ...extraOpts,
   };

Also applies to: 1760-1760

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

In `@test/continuous-test-suite-observability.ts` around lines 63 - 67, The
provider max-token map you added is unused because buildGenerateOptions()
currently hardcodes maxTokens: 50; update buildGenerateOptions() to compute
maxTokens by taking TEST_CONFIG.maxTokens (or its fallback) and clamping it
against the provider caps map (the map entries like deepseek, "nvidia-nim",
"lm-studio", llamacpp) so generated options reflect provider limits, or if you
prefer remove the provider caps map entirely; specifically, modify
buildGenerateOptions() to read TEST_CONFIG.maxTokens, look up the active
provider key in the provider caps map, set maxTokens =
min(TEST_CONFIG.maxTokens, providerCap) and return that in the options (or
delete the unused map if keeping fixed 50 is intended).
docs/provider-integration/01-shared-changes.md (1)

438-445: Section numbering could be made consistent (§8a vs §8).

Consider renumbering the later heading to avoid cross-reference ambiguity in this long checklist doc.

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

In `@docs/provider-integration/01-shared-changes.md` around lines 438 - 445, The
section numbering is inconsistent between "§8a" and "§8" which can confuse
cross-references in this checklist; update the later heading (the one
referencing src/lib/adapters/providerImageAdapter.ts and VISION_CAPABILITIES) so
numbering matches the earlier "§8a" pattern (e.g., change "§8." to "§8b" or
renumber both to a consistent scheme), and verify references to
TOP_MODELS_CONFIG, DEFAULT_MODELS, getDefaultModel(provider), and
getTopModelChoices() remain accurate after the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/provider-integration/00-architecture.md`:
- Around line 246-252: The docs section "Pattern 9: Test integration" is
outdated about test coverage; update the text to mention the new dedicated test
suite and npm script by replacing the claim that "test:providers" is sufficient
with guidance pointing to the new canonical suite and script: reference the new
file test/continuous-test-suite-new-providers.ts and the new npm script name
test:new-providers, and also note that test/continuous-test-suite-providers.ts
still uses ALL_PROVIDERS = [...] as const and that
test/continuous-test-suite-credentials.ts needs four new credential test blocks
— ensure the doc instructs maintainers to add the four provider names into
ALL_PROVIDERS and to add per-call credential tests or rely on
test/continuous-test-suite-new-providers.ts as the canonical coverage.

In `@docs/provider-integration/03-nvidia-nim.md`:
- Around line 289-339: callStream's providerOptions merge currently removes
rejected fields only from extraBody, but because callerBody is re-merged on
retries those fields can reappear and cause repeated 400s; update the merge
logic inside callStream so that after constructing mergedBody and mergedKwargs
you explicitly strip the rejected fields (e.g., reasoning_budget and
chat_template) from mergedBody and mergedKwargs (not just from extraBody) before
returning the providerOptions object, ensuring retries drop caller-supplied
copies as well; modify the closure that builds providerOptions (referencing
mergedBody, mergedKwargs, callerBody, callerBase) to perform this post-merge
strip.

In `@docs/provider-integration/04-lm-studio.md`:
- Around line 111-118: The constructor currently unconditionally sets
this.apiKey = LM_STUDIO_PLACEHOLDER_KEY which prevents real credentials from
being used and makes the Authorization branches in getAvailableModels() and
validateConfiguration() unreachable; update the constructor to use
credentials?.apiKey (falling back to LM_STUDIO_PLACEHOLDER_KEY only when absent)
and pass that real apiKey into createOpenAI(...) and any probe/helper that uses
createProxyFetch() so the auth-proxied LM Studio flows are exercised; ensure the
same change is applied to the other occurrences referenced (around the blocks
using this.baseURL, createOpenAI, getAvailableModels, and
validateConfiguration).

In `@docs/provider-integration/06-testing.md`:
- Around line 135-138: Update the docs section that currently claims "No new
test scripts needed" to reference the new dedicated suite: mention the new
test/continuous-test-suite-new-providers.ts and the pnpm run test:new-providers
script as the canonical way to exercise new-provider integrations, while still
noting test:providers (ALL_PROVIDERS) remains available; change the sentence in
the D. `package.json` paragraph to explicitly list `pnpm run test:new-providers`
and `test/continuous-test-suite-new-providers.ts` so contributors are pointed to
the new-provider suite.

In `@docs/provider-integration/09-test-suite-spec.md`:
- Around line 203-220: Update the "### 3b. Test grouping" list to reflect the
actual test cases implemented in continuous-test-suite-new-providers.ts by
trimming or annotating entries: compare the SECTION 1..10 matrix (A1-A5, B1-B5,
C1-C4, D1-D3, etc.) against the actual test IDs in
continuous-test-suite-new-providers.ts, remove any IDs not present in that file
or append "(planned)" / "(not implemented)" to those missing IDs, and ensure the
per-section bullets only list the concrete test IDs the suite runs.

In `@docs/provider-integration/10-test-results-final.md`:
- Line 112: Update the sentence that mentions "gtimeout" so it uses the
provider-matrix runner’s generic timeout wording (i.e., refer to the runner's
generic "timeout"/"gtimeout" support or simply "timeout (runner-supported)")
instead of naming gtimeout specifically; change the phrase containing the
literal token "gtimeout" to the runner-generic wording to reflect that both
"gtimeout" and "timeout" are supported by the provider-matrix runner.

In `@docs/provider-integration/11-test-failure-investigation.md`:
- Around line 47-52: The summary's Item 4 incorrectly labels the duplicate
failure as "same-as-Bug-2" (LM Studio outage); update the label to reference the
Budget=0 fallback bug instead (e.g., "same-as-Bug-3" or "same-as Budget=0
fallback") so the closing summary consistently matches the root causes listed in
the bullets (reference the items described as "Bug 2", "section 3", and the
fourth bullet).

In `@docs/provider-integration/12-pr-analysis.md`:
- Around line 45-52: Update the pricing notes in
docs/provider-integration/12-pr-analysis.md to reflect the final design: remove
references to symbolic local rates and instead document that LM Studio and
llama.cpp use a provider-level `_default` pricing of zero in
src/lib/utils/pricing.ts (the `_default` sentinel), and ensure any mention of
symbolic $1/M token placeholder is deleted or replaced; also update any related
explanatory text referencing createXConfig helpers in
src/lib/utils/providerConfig.ts and the provider enums in
src/lib/constants/enums.ts so the notes accurately state that those two
providers have zero-cost defaults rather than symbolic local rates.

In `@src/cli/factories/commandFactory.ts`:
- Around line 80-87: The CLI provider choices array in commandFactory.ts is
missing the "nvidia" alias (which ProviderRegistry accepts) causing a mismatch;
update the provider choices list (the array containing "nvidia-nim", "nim",
etc.) to include "nvidia" alongside the existing "nvidia-nim" entry so that the
CLI --provider choices and bash completion accept the same aliases as
ProviderRegistry.

In `@src/lib/constants/contextWindows.ts`:
- Around line 273-278: The provider alias map is missing an entry for Google AI
Studio so normalizeProviderForLookup() yields "googleaistudio" and falls through
to DEFAULT_CONTEXT_WINDOW; add an alias by inserting a mapping from
"googleaistudio" to "google-ai-studio" into the PROVIDER_ALIAS_MAP constant (and
make the same addition in the other occurrence of that map later in the file),
so lookups normalize to the existing "google-ai-studio" key and use the correct
context window table.

In `@src/lib/utils/modelChoices.ts`:
- Around line 318-321: getDefaultModel() returns an empty string for
AIProviderName.LM_STUDIO and AIProviderName.LLAMACPP while getTopModelChoices()
emits AUTO_DISCOVER_CHOICE as "__auto_discover__", causing mismatch when
preselecting the active choice; locate getDefaultModel, getTopModelChoices and
the constant AUTO_DISCOVER_CHOICE and make them use a single sentinel value
(preferably keep runtime sentinel as ""), or normalize AUTO_DISCOVER_CHOICE back
to "" at the CLI/choices boundary before constructing provider options so the
default/current model ("" from getDefaultModel) matches the emitted choice and
LM Studio/llama.cpp auto-discovery is selectable.

In `@src/lib/utils/pricing.ts`:
- Around line 324-327: The provider alias table in pricing.ts is missing
mappings for the common "nim" and "nvidia" inputs, causing
findRates()/calculateCost()/hasPricing() to miss NIM rates; update the mapping
object (the provider lookup where entries like deepseek, nvidianim, lmstudio,
llamacpp are defined) to include keys "nim" and "nvidia" that map to the
canonical "nvidia-nim" value so normalized inputs resolve to the existing NIM
pricing.
- Around line 416-421: The current provider-level `_default` fallback
(providerPricing["_default"]) makes non-billable local providers appear billable
because findRates() returns that zero-rate object and hasPricing() becomes true;
change the fallback logic so providerPricing["_default"] is only returned if it
represents actual billable pricing (e.g., has a non-zero rate or a valid
currency/price field), otherwise treat it as absent and return undefined/null so
findRates()/hasPricing() continue to indicate no pricing for local/non-billable
providers; update the code paths that use providerPricing["_default"] (search
for providerPricing["_default"], findRates, hasPricing) to enforce this check.

In `@test/continuous-test-suite-new-providers.ts`:
- Around line 82-119: The current isExpectedProviderError matcher is downgrading
authentication/401/403 errors for configured providers; change
isExpectedProviderError to accept an optional configured boolean (e.g.,
isExpectedProviderError(msg: string, configured?: boolean): boolean | null) and
normalize msg with toLowerCase() as now, but if configured === true then do NOT
treat authentication-related tokens ("api key", "api_key", "authentication",
"unauthorized", "permission denied", "401", "403", "credentials", "billing",
etc.) as expected provider errors — return null instead so the caller can record
SKIP, otherwise continue to return true/false as before; update call sites that
rely on boolean to handle the new null SKIP sentinel.

In `@test/continuous-test-suite-providers.ts`:
- Around line 1893-1917: The allowlist PIPELINE_A_PROVIDERS is missing
"openai-compatible", causing providers with providerKey derived from
TEST_CONFIG.provider to be treated as failures when generationSpans.length ===
0; add "openai-compatible" to the Set defined as PIPELINE_A_PROVIDERS so that
the if branch treating zero model.generation spans as acceptable for Pipeline A
also accepts providerKey === "openai-compatible".

---

Outside diff comments:
In `@src/lib/proxy/proxyFetch.ts`:
- Around line 766-785: maskProxyUrl currently only redacts URL username/password
but leaves query parameters and the fragment intact, which can leak signed query
credentials; update maskProxyUrl to iterate u.searchParams and replace every
parameter value with a fixed redaction token (e.g. "***") while keeping
parameter names, and also redact u.hash (fragment) by replacing it with
something like "#***" when present, preserving the try/catch and existing
username/password masking in the maskProxyUrl function so the function returns a
safe, masked URL string or the same error result for invalid URLs.

In `@test/continuous-test-suite-credentials.ts`:
- Around line 545-568: The assertion message references a removed identifier
availableLower causing a runtime/type error; update the assert call to use the
existing normalized list (availableNormalized) or the original available array —
e.g., replace availableLower.join(", ") with availableNormalized.join(", ") (or
available.join(", ")) so the message uses a defined symbol; check the assert
near matchCount and ensure availableLower is not referenced elsewhere.

---

Duplicate comments:
In `@docs/provider-integration/03-nvidia-nim.md`:
- Around line 79-85: The import list in the sample is missing the shared type
NvidiaNimExtraBody and the surrounding note contains a duplicated typo
"NvidiaNvidiaNimExtraBody"; update the import statement to include
NvidiaNimExtraBody alongside UnknownRecord, NeurolinkCredentials, StreamOptions,
StreamResult, and ValidationSchema, and correct the note text to reference
NvidiaNimExtraBody exactly (also apply the same fixes in the other affected
block around lines 103-127).
- Around line 489-493: There is a fenced verification code block that lacks
surrounding blank lines (tripping MD031); add a single blank line immediately
before the opening ``` and a single blank line immediately after the closing ```
for the verification block so the markdown linter passes.

In `@docs/provider-integration/04-lm-studio.md`:
- Around line 87-99: The class LMStudioProvider is missing the
requestedModelName field used by the discovery branch; add a private readonly
requestedModelName (or similar) to the class and initialize it in the
constructor from the incoming modelName parameter so that
this.requestedModelName is defined and immutable; keep using this.modelName
(updated by refreshHandlersForModel()) for the live/active model while leaving
this.requestedModelName as the original explicit request so the discovery logic
(and FALLBACK_MODEL / /v1/models retry path) can correctly detect explicit vs
auto-selected models.

In `@docs/provider-integration/05-llamacpp.md`:
- Around line 85-97: The sample class LlamaCppProvider uses requestedModelName
in getAISDKModel() but never declares or assigns it; update the class to declare
a private readonly requestedModelName?: string and initialize it from the
constructor parameter (modelName) so discovery vs explicit selection works
correctly (keep this.requestedModelName immutable while this.modelName can be
updated by refreshHandlersForModel()); ensure getAISDKModel() reads
this.requestedModelName for branching and that refreshHandlersForModel()
continues to update this.modelName when discovery succeeds or falls back.
- Around line 109-116: The sample currently hardcodes LLAMACPP_PLACEHOLDER_KEY
and calls validateConfiguration using the default fetch, so auth/proxy checks
are inconsistent with the docs; change initialization of this.apiKey to prefer
credentials.llamacpp.apiKey or process.env.LLAMACPP_API_KEY instead of
LLAMACPP_PLACEHOLDER_KEY, and ensure createOpenAI is created with the same
proxy-aware fetch (createProxyFetch()) and that validateConfiguration is invoked
with that proxy fetch and the api key/auth headers and a sensible timeout;
update usage points referenced (this.baseURL, this.apiKey,
LLAMACPP_PLACEHOLDER_KEY, createOpenAI, validateConfiguration, createProxyFetch,
credentials.llamacpp.apiKey, LLAMACPP_API_KEY) so the health check uses the real
auth+proxy configuration.

In `@docs/provider-integration/06-testing.md`:
- Around line 67-128: Update the test snippets to match the runner contract:
change assertions that check result?.text to assert on result?.content (e.g., in
the "nvidia-nim" and "lm-studio" blocks replace result?.text with
result?.content), ensure all SKIP paths return null (replace bare return in the
LM Studio skip with return null), and when detecting provider-specific error
messages normalize the string with toLowerCase() before matching to make
detection case-insensitive; locate these changes around the runTest calls and
the NeuroLink.generate usage (provider "nvidia-nim", "lm-studio", "llamacpp")
and update the assertions and skip returns accordingly.

In `@docs/provider-integration/07-implementation-order.md`:
- Around line 91-104: The fenced code blocks in this section violate
markdownlint MD031; add a blank line before and after each triple-backtick fence
so each fenced block is separated from surrounding list items and
text—specifically update the export command block and the two bash examples
under "4. Validate retry-on-400" and "5. Validate vision model" (the ```bash
fences) to have an empty line above the opening ``` and an empty line below the
closing ``` so the linter no longer flags MD031.

In `@docs/provider-integration/09-test-suite-spec.md`:
- Around line 165-199: The snippet mixes old booleans (HAS_LM_STUDIO,
HAS_LLAMACPP) with the newer probe objects and URL vars (LM_STUDIO_PROBE,
LLAMACPP_PROBE, LM_STUDIO_URL, LLAMACPP_URL) which is inconsistent; remove the
stale HAS_* probes and instead call probeLocalServer using the correct
LM_STUDIO_URL and LLAMACPP_URL (and keys) once, then populate
PROVIDERS_UNDER_TEST using LM_STUDIO_PROBE.available / .loadedModel and
LLAMACPP_PROBE.available / .loadedModel so the provider entries are consistent
(update any references to HAS_LM_STUDIO/HAS_LLAMACPP to use the corresponding
PROBE objects).

In `@docs/provider-integration/12-pr-analysis.md`:
- Around line 7-12: The summary line in
docs/provider-integration/12-pr-analysis.md ("13 new Markdown docs") is
inconsistent with the enumerated list (15 entries); open that file and update
the summary count to match the actual list (change "13 new Markdown docs" to "15
new Markdown docs") or alternatively remove/merge the two extra entries in the
docs/provider-integration/ list so both the bullet and the list are consistent;
make the edit in the bullet section shown in the diff so the document and
enumeration align.

In `@src/lib/providers/deepseek.ts`:
- Around line 166-185: The current streamText call unconditionally sets
providerOptions.openai.thinking for all non-reasoner calls and overwrites caller
providerOptions; change this to only enable thinking when the request explicitly
asks for it (e.g., options.thinking) and merge the thinking flag into any
existing options.providerOptions instead of replacing them. Specifically, before
calling streamText (in the same scope as isReasoner and options), build a
mergedProviderOptions that starts from options.providerOptions (without mutating
it) and, if !isReasoner && options.thinking, sets mergedProviderOptions.openai =
{ ...(options.providerOptions?.openai), thinking: { type: "enabled" } }; then
pass mergedProviderOptions to streamText’s providerOptions parameter. Ensure you
reference the streamText call and the isReasoner variable when applying this
change.

In `@src/lib/providers/llamaCpp.ts`:
- Around line 205-225: The executeStream path currently calls getAISDKModel()
before opening the span but executeStreamInner() also triggers discovery,
causing double-discovery and inconsistent telemetry; fix by resolving the model
exactly once per stream request: call await this.getAISDKModel() in
executeStream (as shown), ensure this.discoveredModel / this.modelName is set
and then remove or guard any call to getAISDKModel() inside executeStreamInner
(and any helper it calls) so executeStreamInner uses the already-resolved model
info for telemetry/operations (update references in executeStreamInner,
streamText, or other inner helpers to read this.discoveredModel rather than
re-invoking getAISDKModel()).

In `@src/lib/providers/lmStudio.ts`:
- Around line 213-233: The stream path currently calls getAISDKModel() before
the span but then calls executeStreamInner() which may re-run discovery,
doubling latency and causing inconsistent model attributes; resolve the model
once in executeStream by calling getAISDKModel(), compute the effective model
identifier (using this.modelName || this.discoveredModel || FALLBACK_MODEL) and
pass that resolved model/name into executeStreamInner (update executeStreamInner
signature or provide an explicit parameter such as resolvedModelName) so the
inner logic and the span use the same model; apply the same change pattern to
the analogous execute()/executeInner pair referenced around lines 256-321 to
prevent duplicate discovery there as well.

In `@test/run-provider-matrix.sh`:
- Around line 146-166: The matrix printing loop currently iterates "for suite in
$SUITES context memory;" but the runner only executes "$SUITES", causing
persistent "—" cells for suites never run; change the loop to only iterate the
actual suites the runner uses (replace "for suite in $SUITES context memory;"
with "for suite in $SUITES; do") or, if you intended to include "context" and
"memory" in runs, add them to the SUITES variable where tests are launched so
both the run loop and the summary loop use the same SUITES set (references:
SUITES, the for-suite loop, and the summary file lookup
f="$RESULTS/$p/${suite}.summary").

---

Nitpick comments:
In `@docs/provider-integration/01-shared-changes.md`:
- Around line 438-445: The section numbering is inconsistent between "§8a" and
"§8" which can confuse cross-references in this checklist; update the later
heading (the one referencing src/lib/adapters/providerImageAdapter.ts and
VISION_CAPABILITIES) so numbering matches the earlier "§8a" pattern (e.g.,
change "§8." to "§8b" or renumber both to a consistent scheme), and verify
references to TOP_MODELS_CONFIG, DEFAULT_MODELS, getDefaultModel(provider), and
getTopModelChoices() remain accurate after the change.

In `@test/continuous-test-suite-observability.ts`:
- Around line 63-67: The provider max-token map you added is unused because
buildGenerateOptions() currently hardcodes maxTokens: 50; update
buildGenerateOptions() to compute maxTokens by taking TEST_CONFIG.maxTokens (or
its fallback) and clamping it against the provider caps map (the map entries
like deepseek, "nvidia-nim", "lm-studio", llamacpp) so generated options reflect
provider limits, or if you prefer remove the provider caps map entirely;
specifically, modify buildGenerateOptions() to read TEST_CONFIG.maxTokens, look
up the active provider key in the provider caps map, set maxTokens =
min(TEST_CONFIG.maxTokens, providerCap) and return that in the options (or
delete the unused map if keeping fixed 50 is intended).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3724491e-219b-499c-bc45-c18bdc63e9cb

📥 Commits

Reviewing files that changed from the base of the PR and between f0cb633 and c8a2549.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (51)
  • .env.example
  • docs/provider-integration/00-architecture.md
  • docs/provider-integration/01-shared-changes.md
  • docs/provider-integration/02-deepseek.md
  • docs/provider-integration/03-nvidia-nim.md
  • docs/provider-integration/04-lm-studio.md
  • docs/provider-integration/05-llamacpp.md
  • docs/provider-integration/06-testing.md
  • docs/provider-integration/07-implementation-order.md
  • docs/provider-integration/08-feature-matrix.md
  • docs/provider-integration/09-test-suite-spec.md
  • docs/provider-integration/10-test-results-final.md
  • docs/provider-integration/11-test-failure-investigation.md
  • docs/provider-integration/12-pr-analysis.md
  • docs/provider-integration/13-code-review.md
  • docs/provider-integration/README.md
  • package.json
  • src/cli/factories/commandFactory.ts
  • src/lib/adapters/providerImageAdapter.ts
  • src/lib/constants/contextWindows.ts
  • src/lib/constants/enums.ts
  • src/lib/core/baseProvider.ts
  • src/lib/factories/providerRegistry.ts
  • src/lib/providers/deepseek.ts
  • src/lib/providers/index.ts
  • src/lib/providers/llamaCpp.ts
  • src/lib/providers/lmStudio.ts
  • src/lib/providers/nvidiaNim.ts
  • src/lib/proxy/proxyFetch.ts
  • src/lib/types/providers.ts
  • src/lib/utils/modelChoices.ts
  • src/lib/utils/pricing.ts
  • src/lib/utils/providerConfig.ts
  • test/continuous-test-suite-client.ts
  • test/continuous-test-suite-context.ts
  • test/continuous-test-suite-credentials.ts
  • test/continuous-test-suite-evaluation-scoring.ts
  • test/continuous-test-suite-evaluation.ts
  • test/continuous-test-suite-mcp-http.ts
  • test/continuous-test-suite-mcp.ts
  • test/continuous-test-suite-media-gen.ts
  • test/continuous-test-suite-memory.ts
  • test/continuous-test-suite-new-providers.ts
  • test/continuous-test-suite-observability.ts
  • test/continuous-test-suite-ppt.ts
  • test/continuous-test-suite-providers.ts
  • test/continuous-test-suite-session-memory-bugs.ts
  • test/continuous-test-suite-tts.ts
  • test/continuous-test-suite-workflow.ts
  • test/continuous-test-suite.ts
  • test/run-provider-matrix.sh

Comment thread docs/provider-integration/00-architecture.md Outdated
Comment on lines +289 to +339
const callStream = (body: NvidiaNimExtraBody) =>
streamText({
model,
messages,
temperature: options.temperature,
maxOutputTokens: options.maxTokens,
tools,
stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
abortSignal: composeAbortSignals(
options.abortSignal,
timeoutController?.controller.signal,
),
providerOptions: (() => {
// Merge with caller-supplied providerOptions instead of replacing.
// Per-call overrides win; chat_template_kwargs is merged shallowly
// so an override of one field doesn't drop env-driven flags.
const callerBase =
((options as unknown as Record<string, unknown>)
.providerOptions as Record<string, unknown> | undefined) ?? {};
const callerOpenai =
(callerBase.openai as Record<string, unknown> | undefined) ?? {};
const callerBody =
(callerOpenai.body as Record<string, unknown> | undefined) ?? {};
const defaultsBody = body as unknown as Record<string, unknown>;
const mergedBody: Record<string, unknown> = {
...defaultsBody,
...callerBody,
};
const mergedKwargs = {
...((defaultsBody.chat_template_kwargs as
| Record<string, unknown>
| undefined) ?? {}),
...((callerBody.chat_template_kwargs as
| Record<string, unknown>
| undefined) ?? {}),
};
if (Object.keys(mergedKwargs).length > 0) {
mergedBody.chat_template_kwargs = mergedKwargs;
}
if (
Object.keys(callerBase).length === 0 &&
Object.keys(mergedBody).length === 0
) {
return undefined;
}
return {
...callerBase,
openai: { ...callerOpenai, body: mergedBody },
};
})(),

ghost Apr 29, 2026

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 | 🟠 Major

Retry must strip rejected fields after the merge, not just from extraBody.

callStream() re-merges callerBody on every attempt. Stripping reasoning_budget or chat_template from extraBody alone means caller-supplied copies can be reintroduced on the retry, reproducing the same 400.

Based on learnings: In juspay/neurolink PR #997, the NIM 400 retry path in nvidiaNim.ts strips reasoning_budget/chat_template from mergedBody/mergedKwargs AFTER the merge step (not before), so caller-supplied copies of the rejected fields are also dropped from the retry request. Strip-before-merge would leave caller copies intact and re-trigger the 400.

Also applies to: 376-385

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

In `@docs/provider-integration/03-nvidia-nim.md` around lines 289 - 339,
callStream's providerOptions merge currently removes rejected fields only from
extraBody, but because callerBody is re-merged on retries those fields can
reappear and cause repeated 400s; update the merge logic inside callStream so
that after constructing mergedBody and mergedKwargs you explicitly strip the
rejected fields (e.g., reasoning_budget and chat_template) from mergedBody and
mergedKwargs (not just from extraBody) before returning the providerOptions
object, ensuring retries drop caller-supplied copies as well; modify the closure
that builds providerOptions (referencing mergedBody, mergedKwargs, callerBody,
callerBase) to perform this post-merge strip.

Comment thread docs/provider-integration/04-lm-studio.md
Comment thread docs/provider-integration/06-testing.md
Comment thread docs/provider-integration/09-test-suite-spec.md Outdated
Comment thread src/lib/utils/modelChoices.ts
Comment thread src/lib/utils/pricing.ts
Comment thread src/lib/utils/pricing.ts
Comment on lines +82 to +119
function isExpectedProviderError(msg: string): boolean {
const lower = msg.toLowerCase();
return [
"api key",
"api_key",
"authentication",
"rate limit",
"quota",
"credentials",
"cannot connect",
"not configured",
"permission denied",
"billing",
"econnrefused",
"enotfound",
"unauthorized",
"403",
"429",
"could not resolve",
"network",
"no providers",
"failed to fetch",
"fetch failed",
"deepseek_api_key",
"nvidia_nim_api_key",
"server not reachable",
// LM Studio / llama.cpp connectivity / auth phrases only —
// bare "lm studio" was too broad and would mask real regressions.
"lm studio server not reachable",
"lm studio connection refused",
"lm studio api key",
"lm studio unauthorized",
"llama.cpp server not",
"llama.cpp connection refused",
"no model loaded",
"model_not_found",
].some((p) => lower.includes(p));
}

ghost Apr 29, 2026

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 | 🟠 Major

Don't downgrade auth failures to SKIP for configured providers.

The availability gate already handles missing env/probe state. With the current matcher, a configured provider that starts returning 401/403/authentication errors gets recorded as SKIP, so the suite can go green on a broken integration instead of failing.

🛠️ Suggested direction
-function isExpectedProviderError(msg: string): boolean {
+function isExpectedProviderError(
+  msg: string,
+  provider: ProviderUnderTest,
+): boolean {
   const lower = msg.toLowerCase();
-  return [
-    "api key",
-    "api_key",
-    "authentication",
-    "rate limit",
-    "quota",
-    "credentials",
-    "cannot connect",
-    "not configured",
-    "permission denied",
-    "billing",
-    "econnrefused",
-    "enotfound",
-    "unauthorized",
-    "403",
-    "429",
-    "could not resolve",
-    "network",
-    "no providers",
-    "failed to fetch",
-    "fetch failed",
-    ...
-  ].some((p) => lower.includes(p));
+  const transportOrMissingConfig = [
+    "cannot connect",
+    "not configured",
+    "econnrefused",
+    "enotfound",
+    "could not resolve",
+    "no providers",
+    "failed to fetch",
+    "fetch failed",
+    "server not reachable",
+  ];
+  const authOrBilling = [
+    "api key",
+    "api_key",
+    "authentication",
+    "credentials",
+    "permission denied",
+    "billing",
+    "unauthorized",
+    "403",
+    "429",
+    "quota",
+  ];
+
+  return (
+    transportOrMissingConfig.some((p) => lower.includes(p)) ||
+    (!provider.available && authOrBilling.some((p) => lower.includes(p)))
+  );
 }
 ...
-    if (isExpectedProviderError(msg)) {
+    if (isExpectedProviderError(msg, provider)) {

Based on learnings: In continuous test suites, use case-insensitive provider error detection by normalizing messages with toLowerCase() and represent skipped scenarios by returning null (with SKIP status) to avoid false passes.

Also applies to: 288-294

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

In `@test/continuous-test-suite-new-providers.ts` around lines 82 - 119, The
current isExpectedProviderError matcher is downgrading authentication/401/403
errors for configured providers; change isExpectedProviderError to accept an
optional configured boolean (e.g., isExpectedProviderError(msg: string,
configured?: boolean): boolean | null) and normalize msg with toLowerCase() as
now, but if configured === true then do NOT treat authentication-related tokens
("api key", "api_key", "authentication", "unauthorized", "permission denied",
"401", "403", "credentials", "billing", etc.) as expected provider errors —
return null instead so the caller can record SKIP, otherwise continue to return
true/false as before; update call sites that rely on boolean to handle the new
null SKIP sentinel.

Comment thread test/continuous-test-suite-providers.ts
@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

Round 10 (HEAD 895a4ed) — final productive round. Addressed all 15 actionable comments from the eleventh CodeRabbit pass:

Major / correctness

  • src/lib/constants/contextWindows.ts PROVIDER_ALIAS_MAP: added googleaistudio → google-ai-studio, nim → nvidia-nim, nvidia → nvidia-nim so AI Studio + NIM alias forms find the correct context window instead of falling through to DEFAULT_CONTEXT_WINDOW.
  • src/lib/utils/pricing.ts PROVIDER_ALIASES: same — added nim and nvidia so calculateCost("nim", ...) / hasPricing("nvidia", ...) resolve to the canonical NIM rates.
  • src/lib/utils/pricing.ts hasPricing: now returns false for zero-rate _default entries. Local providers (lm-studio / llamacpp) explicitly don't have an upstream USD price, and any caller gated by hasPricing() should treat them as non-billable instead of zero-cost-billable.
  • src/lib/utils/modelChoices.ts getTopModelChoices: unified the auto-discovery sentinel — emits the same "" value as DEFAULT_MODELS (with a friendly "Auto-discover loaded model" label) so any caller that preselects via getDefaultModel(provider) resolves to the right choice for LM Studio / llama.cpp.
  • test/continuous-test-suite-new-providers.ts isExpectedProviderError: split into two phrase lists. Transport / missing-config errors still SKIP unconditionally; auth/billing errors only SKIP when the provider isn't marked available. A configured provider that suddenly returns 401/403/billing now FAILs the suite instead of silently going green over a broken integration.
  • test/continuous-test-suite-providers.ts PIPELINE_A_PROVIDERS: added openai-compatible. Without it, --provider=openai-compatible would have failed the observability-spans test even though the provider also goes through Pipeline A.

Minor

  • src/cli/factories/commandFactory.ts: added nvidia and lms CLI aliases so --provider=nvidia / --provider=lms no longer fail validation even though the registry already accepted them.
  • docs/provider-integration/00-architecture.md (Pattern 9): added pointer to test:new-providers script and the dedicated suite as the canonical entrypoint for the four integrations.
  • docs/provider-integration/04-lm-studio.md ctor snippet: this.apiKey reads from credentials → env → placeholder so the Authorization branches in getAvailableModels and validateConfiguration aren't dead code.
  • docs/provider-integration/06-testing.md §D: same test:new-providers pointer added.
  • docs/provider-integration/09-test-suite-spec.md §3b: trimmed the test-ID list to what's actually shipped (A1-A5, B1/B2/B4, C1, D1, E1/E2, H1, I1, J1/J2, K1/K2/K5, L1) with an explicit note that the other slots (B3, B5, C2-C4, etc.) are reserved for future expansion, not currently exercised.
  • docs/provider-integration/10-test-results-final.md L112: gtimeout → gtimeout/timeout matching the run-provider-matrix.sh autodetection.
  • docs/provider-integration/11-test-failure-investigation.md item 4: same-as-Bug-2 → references Bug 3 (Budget=0) which is the actual duplicate root cause.
  • docs/provider-integration/12-pr-analysis.md: pricing.ts row updated — local rates are 0 (not symbolic), and hasPricing() returns false for zero entries.

Verification: pnpm run check 0 errors, pnpm run lint 0 errors (18 pre-existing warnings unchanged), pnpm run format applied.

Single-commit policy followed (--amend + --force-with-lease).

This is round 10/10 — the loop hits its productive-round cap with this push and will stop after the next no-op tick.

@coderabbitai full review please

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown

@murdore — thank you for the thorough Round 10 summary. The fixes are well-reasoned:

  • PROVIDER_ALIAS_MAP / PROVIDER_ALIASES extensions (googleaistudio, nim, nvidia): consistent alias coverage across both contextWindows.ts and pricing.ts ensures callers using shorthand provider names get correct context windows and pricing rates rather than silent fallbacks.
  • hasPricing returns false for zero-rate _default entries: this is semantically important — local providers have no upstream USD billing, so callers gated by hasPricing() should treat them as non-billable (not zero-cost-billable), which is a meaningful distinction for analytics and cost-reporting logic.
  • isExpectedProviderError two-phase split: the previous single-list approach would silently SKIP a 401/403 from a fully-configured provider, masking broken integrations. Splitting into unconditional-transport-SKIP vs availability-gated-auth-SKIP makes the suite an actual regression gate for configured providers.
  • PIPELINE_A_PROVIDERS + openai-compatible: closing the gap where --provider=openai-compatible would have incorrectly FAILed the observability-spans test.
  • getTopModelChoices unified sentinel (""): aligning the emitted value with DEFAULT_MODELS ensures CLI preselection and model-choice roundtrips are consistent for LM Studio / llama.cpp.
  • nvidia / lms CLI aliases: closing the validation gap between what the registry accepts and what the CLI choices list validates against.

Kicking off a fresh full review against 895a4ed1 now.


🧠 Learnings used
Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-29T14:32:52.926Z
Learning: In juspay/neurolink PR `#997`, `ProviderConfigOptions` has an `optional?: boolean` flag. When set to `true` (used by `createLmStudioConfig` and `createLlamaCppConfig`), `validateApiKey`/`validateApiKeyEnhanced` return `""` instead of throwing, so local providers using the env var as a base URL rather than a secret key are not reported as "unconfigured".

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-17T04:30:25.521Z
Learning: In neurolink (src/lib/neurolink.ts), the `pipelineAHandled: true` flag on `generation:end` events is set ONLY by `finalizeGenerateRequestResult` (the SDK-level wrapper). Native providers (amazonBedrock.ts, ollama.ts, googleAiStudio.ts, googleVertex.ts) emit their own `generation:end` events WITHOUT this flag. The `initializeMetricsListeners()` early-return guard `if (data.pipelineAHandled) return` correctly prevents duplicates: it skips the SDK-level event while allowing native provider events through to Pipeline B.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-29T14:32:52.926Z
Learning: In juspay/neurolink PR `#997`, lmStudio.ts and llamaCpp.ts use a `requestedModelName` (constructor-captured, never mutated) to distinguish explicit model selection from auto-discovery. `this.modelName` is updated by `refreshHandlersForModel()` on discovery success/fallback, but the discovery-vs-explicit branch always reads `requestedModelName` so that a FALLBACK_MODEL assignment never short-circuits future `/v1/models` retries.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-29T14:32:52.926Z
Learning: In juspay/neurolink PR `#997`, the NIM 400 retry path in nvidiaNim.ts strips `reasoning_budget`/`chat_template` from `mergedBody`/`mergedKwargs` AFTER the merge step (not before), so caller-supplied copies of the rejected fields are also dropped from the retry request. Strip-before-merge would leave caller copies intact and re-trigger the 400.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.

Learnt from: Yaswanth-2874
Repo: juspay/neurolink PR: 880
File: src/lib/neurolink.ts:6191-6195
Timestamp: 2026-03-17T11:20:55.110Z
Learning: In neurolink stream fallback logging (src/lib/neurolink.ts handleStreamFallback), typical usage is to override both provider and model together, but logs should still accurately reflect one-sided overrides when only model or only provider is set.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-26T06:27:32.943Z
Learning: In juspay/neurolink PR `#989`, the `generation:end` dedup for `sdk.stream()` uses a per-call options-based context: `enhancedOptions._streamDedupContext` (type `StreamGenerationEndContext = { providerEmitted: boolean }`) is created fresh per `sdk.stream()` invocation. Native providers (googleVertex.ts, googleAiStudio.ts) call `markStreamProviderEmittedGenerationEnd(options)` before their emit; the `runStandardStreamRequest` finally block reads `dedupContext.providerEmitted` and skips the orchestration emit when already true. amazonBedrock.ts and ollama.ts have independent stream paths that never enter `runStandardStreamRequest` so they do not need instrumentation. The orchestration emit carries `pipelineAHandled: true` while native provider emits do not.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 174
File: todos/refactor/03-providers-module.md:3-6
Timestamp: 2025-09-28T21:08:19.655Z
Learning: RajuSudhar mentioned that remaining interfaces in the SageMaker module (30+ interfaces across 5 files) will be resolved in a different PR, but no specific PR was found. The providers module refactor was marked COMPLETED prematurely while significant SageMaker interface conversion work remains pending.

Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-24T03:50:12.966Z
Learning: Applies to src/lib/types/providers.ts : Add new provider names to `AIProviderName` enum in `src/lib/types/providers.ts`
✅ Actions performed

Full review triggered.

@murdore
murdore force-pushed the feat/provider-intgeration branch from 895a4ed to 8c6ac5a Compare April 29, 2026 21:05
@murdore

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai are you doing a full review? Why are you not reviewing properly?

@coderabbitai

ghost commented Apr 29, 2026

Copy link
Copy Markdown

@murdore — I apologize for the confusion. The full review requests have been queued and executed, but after 10 thorough rounds of fixes addressing 100+ actionable comments, the codebase is in very strong shape and the most recent passes have been producing fewer findings as expected.

To confirm everything is working correctly, I'm kicking off another full review right now against the latest HEAD (895a4ed1). If there are any remaining issues, they will surface in the review comments as usual. If the pass comes back clean or near-clean, that's a genuine signal that the PR is in merge-ready condition — which, given the extensive remediation work you've done across all 10 rounds, would be well-deserved.


🧠 Learnings used
Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-29T14:32:52.926Z
Learning: In juspay/neurolink PR `#997`, `ProviderConfigOptions` has an `optional?: boolean` flag. When set to `true` (used by `createLmStudioConfig` and `createLlamaCppConfig`), `validateApiKey`/`validateApiKeyEnhanced` return `""` instead of throwing, so local providers using the env var as a base URL rather than a secret key are not reported as "unconfigured".

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-17T04:30:25.521Z
Learning: In neurolink (src/lib/neurolink.ts), the `pipelineAHandled: true` flag on `generation:end` events is set ONLY by `finalizeGenerateRequestResult` (the SDK-level wrapper). Native providers (amazonBedrock.ts, ollama.ts, googleAiStudio.ts, googleVertex.ts) emit their own `generation:end` events WITHOUT this flag. The `initializeMetricsListeners()` early-return guard `if (data.pipelineAHandled) return` correctly prevents duplicates: it skips the SDK-level event while allowing native provider events through to Pipeline B.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-29T14:32:52.926Z
Learning: In juspay/neurolink PR `#997`, lmStudio.ts and llamaCpp.ts use a `requestedModelName` (constructor-captured, never mutated) to distinguish explicit model selection from auto-discovery. `this.modelName` is updated by `refreshHandlersForModel()` on discovery success/fallback, but the discovery-vs-explicit branch always reads `requestedModelName` so that a FALLBACK_MODEL assignment never short-circuits future `/v1/models` retries.

Learnt from: murdore
Repo: juspay/neurolink PR: 0
File: :0-0
Timestamp: 2026-04-29T14:32:52.926Z
Learning: In juspay/neurolink PR `#997`, the NIM 400 retry path in nvidiaNim.ts strips `reasoning_budget`/`chat_template` from `mergedBody`/`mergedKwargs` AFTER the merge step (not before), so caller-supplied copies of the rejected fields are also dropped from the retry request. Strip-before-merge would leave caller copies intact and re-trigger the 400.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/types/index.ts:58-62
Timestamp: 2025-09-17T18:14:34.960Z
Learning: RajuSudhar explained that in the Neurolink codebase, there are multiple ProviderConfig types causing inconsistency. One existing ProviderConfig type better suited the "ProviderConfig" name, so they renamed the less-suitable one to AIModelProviderConfig to free up the name. Adding backward compatibility aliases would worsen naming inconsistency rather than help. The remaining duplicates will be systematically deduplicated in the 07-Types-Module.md TODO as part of their phased refactor approach.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 173
File: src/lib/index.ts:16-16
Timestamp: 2025-09-17T17:55:15.261Z
Learning: In src/lib/types/providers.ts, ProviderConfig was renamed to AIModelProviderConfig to deduplicate type names, as there was an existing ProviderConfig type that better suited the "ProviderConfig" name. This was an intentional breaking change for better type organization.

Learnt from: Yaswanth-2874
Repo: juspay/neurolink PR: 880
File: src/lib/neurolink.ts:6191-6195
Timestamp: 2026-03-17T11:20:55.110Z
Learning: In neurolink stream fallback logging (src/lib/neurolink.ts handleStreamFallback), typical usage is to override both provider and model together, but logs should still accurately reflect one-sided overrides when only model or only provider is set.

Learnt from: RajuSudhar
Repo: juspay/neurolink PR: 174
File: todos/refactor/03-providers-module.md:3-6
Timestamp: 2025-09-28T21:08:19.655Z
Learning: RajuSudhar mentioned that remaining interfaces in the SageMaker module (30+ interfaces across 5 files) will be resolved in a different PR, but no specific PR was found. The providers module refactor was marked COMPLETED prematurely while significant SageMaker interface conversion work remains pending.

Learnt from: CR
Repo: juspay/neurolink PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-24T03:50:12.966Z
Learning: Applies to src/lib/types/providers.ts : Add new provider names to `AIProviderName` enum in `src/lib/types/providers.ts`
✅ Actions performed

Full review triggered.

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

ghost left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (3)
src/lib/proxy/proxyFetch.ts (1)

772-786: ⚠️ Potential issue | 🟠 Major

Redact query params in maskProxyUrl before logging/export use.

Line 768-Line 770 says signed query params must not reach logs, but Line 782 currently returns the full query string unchanged. This can leak signed URLs/tokens in debug/warn paths.

🔒 Proposed fix
 export function maskProxyUrl(url: string | null | undefined): string | null {
   if (!url) {
     return null;
   }
   try {
     const u = new URL(url);
     if (u.username || u.password) {
       u.username = "***";
       u.password = "***";
     }
+    if (u.search) {
+      for (const key of u.searchParams.keys()) {
+        u.searchParams.set(key, "***");
+      }
+    }
     return u.toString();
   } catch {
     return "[invalid-url]";
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/proxy/proxyFetch.ts` around lines 772 - 786, maskProxyUrl currently
returns the full URL including query string which can leak signed tokens; modify
maskProxyUrl to redact query parameters before returning by parsing the URL
(const u = new URL(url)), replacing username/password as already done, then
clear or sanitize the query portion (e.g., set u.search = "" or iterate
u.searchParams and replace each value with "***") so no raw query values are
returned, and finally return u.toString(); keep the existing try/catch and
invalid-url behavior.
src/lib/adapters/providerImageAdapter.ts (1)

57-76: ⚠️ Potential issue | 🟡 Minor

Normalize getSupportedModels() through the same alias helper.

After this change, supportsVision() accepts aliases like lmstudio, llama.cpp, and nvidianim, but getSupportedModels() still looks up VISION_CAPABILITIES with provider.toLowerCase(). That makes the two public helpers disagree for the same provider spelling.

Suggested follow-up
   static getSupportedModels(provider: string): string[] {
-    const normalizedProvider = provider.toLowerCase();
+    const normalizedProvider = normalizeVisionProvider(provider);
     const models =
       VISION_CAPABILITIES[
         normalizedProvider as keyof typeof VISION_CAPABILITIES
       ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/adapters/providerImageAdapter.ts` around lines 57 - 76,
getSupportedModels is still using provider.toLowerCase() to index
VISION_CAPABILITIES while supportsVision uses normalizeVisionProvider, causing
inconsistent behavior for aliases; update getSupportedModels to call
normalizeVisionProvider(provider) (rather than provider.toLowerCase()) before
looking up VISION_CAPABILITIES so both helpers canonicalize aliases the same
way, and ensure the returned key matches the expectations used in
normalizeVisionProvider (e.g., "lm-studio", "nvidia-nim", "llamacpp",
"google-ai", "openrouter").
test/continuous-test-suite-credentials.ts (1)

545-568: ⚠️ Potential issue | 🔴 Critical

Fix unresolved identifier availableLower.

Line 567 references availableLower, which is never defined. Replace with availableNormalized (defined on line 549).

Suggested fix
     assert(
       matchCount >= 5,
       `Expected at least 5 credential keys to match registered providers. ` +
-        `Got ${matchCount} matches. Available providers: ${availableLower.join(", ")}`,
+        `Got ${matchCount} matches. Available providers: ${availableNormalized.join(", ")}`,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-credentials.ts` around lines 545 - 568, The test
references an undefined identifier availableLower in the assertion; replace that
with the already-defined availableNormalized (or available if you intended
original values) so the assertion shows correct provider names. Update the
assertion message to use availableNormalized.join(", ") (keeping the existing
matchCount check), and verify this uses the normalization function norm and
variables credKeys, available, and matchCount used above.
♻️ Duplicate comments (11)
src/lib/constants/contextWindows.ts (1)

273-275: ⚠️ Potential issue | 🟠 Major

google-ai-studio now normalizes to a table key that does not exist.

PROVIDER_ALIAS_MAP maps googleaistudio to "google-ai-studio", but this registry still stores Gemini windows under "google-ai". As written, getContextWindowSize("google-ai-studio", ...) falls through to DEFAULT_CONTEXT_WINDOW instead of the Gemini limits.

💡 Minimal fix
 const PROVIDER_ALIAS_MAP: Record<string, string> = {
-  googleaistudio: "google-ai-studio",
+  googleaistudio: "google-ai",
   lmstudio: "lm-studio",
   llamacpp: "llamacpp",
   nvidianim: "nvidia-nim",

Also applies to: 323-327

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

In `@src/lib/constants/contextWindows.ts` around lines 273 - 275,
PROVIDER_ALIAS_MAP currently maps "googleaistudio" -> "google-ai-studio" but the
context windows registry uses the key "google-ai", so
getContextWindowSize("google-ai-studio", ...) falls back to
DEFAULT_CONTEXT_WINDOW; update the mapping in PROVIDER_ALIAS_MAP to map
"googleaistudio" -> "google-ai" (and similarly fix any other alias entries that
point to "-studio" instead of the registry key), and ensure getContextWindowSize
is using PROVIDER_ALIAS_MAP to normalize provider names before looking up the
context window sizes.
src/cli/factories/commandFactory.ts (1)

80-89: ⚠️ Potential issue | 🟡 Minor

Keep the CLI alias lists in sync with ProviderRegistry.

ProviderRegistry accepts llama-cpp, and the CLI now accepts nvidia/lms in the main parser, but the yargs choices list still rejects llama-cpp and Line 3933 still omits nvidia, lms, and llama-cpp from bash completion. That leaves supported aliases failing at parse time or disappearing from tab completion.

Suggested patch
       choices: [
         "auto",
         "openai",
@@
         "lms",
         "llamacpp",
         "llama.cpp",
+        "llama-cpp",
       ],
-                    COMPREPLY=( $(compgen -W "auto openai openai-compatible openrouter or bedrock vertex googleVertex anthropic anthropic-subscription azure google-ai google-ai-studio huggingface ollama mistral litellm sagemaker deepseek ds nvidia-nim nim lm-studio lmstudio llamacpp llama.cpp" -- ${cur}) )
+                    COMPREPLY=( $(compgen -W "auto openai openai-compatible openrouter or bedrock vertex googleVertex anthropic anthropic-subscription azure google-ai google-ai-studio huggingface ollama mistral litellm sagemaker deepseek ds nvidia-nim nim nvidia lm-studio lmstudio lms llamacpp llama.cpp llama-cpp" -- ${cur}) )

As per coding guidelines: "Add new providers to CLI provider choices in src/cli/factories/commandFactory.ts".

Also applies to: 3933-3933

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

In `@src/cli/factories/commandFactory.ts` around lines 80 - 89, The CLI's yargs
provider choices and bash completion list in src/cli/factories/commandFactory.ts
are out of sync with ProviderRegistry: add the missing provider aliases
("llama-cpp", and also include "nvidia" and "lms" where appropriate) to the
yargs choices array (the list currently containing "nvidia", "lms", "llamacpp",
"llama.cpp") and to the bash/tab-completion generation so those aliases are
accepted at parse time and show up in completion; update any variables or
constants used for provider choices/completion generation in commandFactory.ts
to include "llama-cpp", "nvidia", and "lms" to match ProviderRegistry.
test/continuous-test-suite-evaluation.ts (1)

1607-1620: ⚠️ Potential issue | 🟡 Minor

Normalize ds and nvidia here too.

This alias map still misses the new-provider shorthands that the CLI/registry accept. Running the suite with --provider=ds or --provider=nvidia falls through to the 1024 fallback instead of the intended DeepSeek/NIM budgets, which can bring back the under-budget failures this block is trying to avoid.

Suggested patch
   const PROVIDER_KEY_ALIASES: Record<string, string> = {
+    ds: "deepseek",
     googleai: "google-ai-studio",
     googleaistudio: "google-ai-studio",
     googlevertex: "vertex",
+    nvidia: "nvidia-nim",
     nvidianim: "nvidia-nim",
     lmstudio: "lm-studio",
     llamacpp: "llamacpp",
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-evaluation.ts` around lines 1607 - 1620, The alias
map PROVIDER_KEY_ALIASES is missing the CLI shorthands for DeepSeek and NVIDIA,
so add entries mapping "ds" to the DeepSeek canonical key (e.g., "deepseek") and
"nvidia" to "nvidia-nim" inside the PROVIDER_KEY_ALIASES object so the canonical
lookup used to set canonical (and subsequently TEST_CONFIG.maxTokens) resolves
to the correct provider budgets instead of falling back to 1024.
docs/provider-integration/07-implementation-order.md (1)

90-104: ⚠️ Potential issue | 🟡 Minor

Add blank lines around the fenced commands in Milestone 4.

These code fences are still tripping MD031, so the doc stays lint-dirty.

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

In `@docs/provider-integration/07-implementation-order.md` around lines 90 - 104,
Add a blank line before and after each fenced code block in Milestone 4 so
Markdown lint rule MD031 is satisfied; specifically, insert an empty line above
and below the three triple-backtick bash blocks that start with "export
NVIDIA_NIM_API_KEY..." (base case), the retry-on-400 block beginning with "# Use
a model that doesn't support reasoning_budget", and the vision model block
starting with "pnpm run cli generate... --image ./test.jpg" to ensure the fences
are surrounded by blank lines.
test/run-provider-matrix.sh (1)

146-146: ⚠️ Potential issue | 🟠 Major

Drop the synthetic context / memory rows or execute them too.

The runner only executes suites from $SUITES, but the matrix renderer still appends context memory. That leaves misleading empty cells for suites that were never run.

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

In `@test/run-provider-matrix.sh` at line 146, The loop currently hardcodes
"context memory" after $SUITES (for suite in $SUITES context memory;), which
causes the matrix renderer to output rows that are never executed; either remove
those synthetic literals from the loop or ensure they are part of $SUITES and
actually executed. Fix by updating the iteration to only use $SUITES (for suite
in $SUITES;) or by appending "context" and "memory" to the SUITES variable
earlier (e.g. export SUITES="$SUITES context memory") so the runner and matrix
stay in sync; target the loop that iterates over suites and the code that
sets/exports SUITES to make the change.
docs/provider-integration/06-testing.md (1)

67-83: ⚠️ Potential issue | 🟡 Minor

Keep these example tests on the suite’s PASS/FAIL/SKIP contract.

The NVIDIA NIM and LM Studio examples still assert result?.text, and the skip paths here still use bare return. In these suites, skipped cases should return null, and generate() success checks should read result?.content.

Suggested doc fix
 // NVIDIA NIM credential override
 await runTest("nvidia-nim per-call apiKey override", async () => {
   if (!process.env.NVIDIA_NIM_API_KEY) {
     console.log(
       `  ${colors.yellow}[SKIP]${colors.reset} NVIDIA_NIM_API_KEY not set`,
     );
     return null;
   }
   const nl = new NeuroLink();
   const result = await nl.generate({
     input: { text: "Reply with PONG only." },
     provider: "nvidia-nim",
     credentials: { nvidiaNim: { apiKey: process.env.NVIDIA_NIM_API_KEY } },
     maxTokens: 16,
   });
-  assert(typeof result?.text === "string", "should return text");
+  assert(typeof result?.content === "string", "should return content");
 });

 // LM Studio credential override (baseURL only)
 await runTest("lm-studio per-call baseURL override", async () => {
   const url = process.env.LM_STUDIO_BASE_URL || "http://localhost:1234/v1";
   // Skip if server not reachable
   try {
     const r = await fetch(`${url.replace(/\/$/, "")}/models`);
     if (!r.ok) throw new Error("not ok");
   } catch {
     console.log(
       `  ${colors.yellow}[SKIP]${colors.reset} LM Studio not running at ${url}`,
     );
-    return;
+    return null;
   }
   const nl = new NeuroLink();
   const result = await nl.generate({
     input: { text: "Reply with PONG only." },
     provider: "lm-studio",
     credentials: { lmStudio: { baseURL: url } },
     maxTokens: 16,
   });
-  assert(typeof result?.text === "string", "should return text");
+  assert(typeof result?.content === "string", "should return content");
 });

 await runTest("nvidia-nim retry strips reasoning_budget on 400", async () => {
   if (!process.env.NVIDIA_NIM_API_KEY) {
     console.log(`  [SKIP] NIM not configured`);
-    return;
+    return null;
   }
   const nl = new NeuroLink();
   // Use a model known NOT to support reasoning_budget — should still succeed via retry
   const result = await nl.generate({
     input: { text: "Hi" },
     provider: "nvidia-nim",
     model: "google/gemma-3-27b-it", // doesn't support reasoning_budget
     thinkingLevel: "high", // forces our code to add the field
     maxTokens: 32,
   });
-  assert(typeof result?.text === "string", "should succeed via retry");
+  assert(typeof result?.content === "string", "should succeed via retry");
 });

Based on learnings, in continuous test suites, use case-insensitive provider error detection by normalizing messages with toLowerCase() and represent skipped scenarios by returning null (with SKIP status) to avoid false passes.

Also applies to: 85-106, 143-159

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

In `@docs/provider-integration/06-testing.md` around lines 67 - 83, The tests use
the old assertions and skip behavior: in the runTest block creating new
NeuroLink() and calling generate({ provider: "nvidia-nim", ... }) change the
skip path to explicitly return null (not bare return) and update success checks
to assert typeof result?.content === "string" instead of result?.text; also
normalize provider error messages with toLowerCase() before matching (e.g., when
detecting provider-specific errors) to make detection case-insensitive. Apply
the same changes to the other NVIDIA NIM and LM Studio examples mentioned (the
blocks around lines 85-106 and 143-159).
docs/provider-integration/05-llamacpp.md (1)

85-116: ⚠️ Potential issue | 🟠 Major

The spec snippet still omits state that later code depends on.

getAISDKModel() branches on this.requestedModelName, and model discovery only sends bearer auth when this.apiKey is real, but the class never declares/captures requestedModelName and the constructor always hardcodes the placeholder key. Copied as-is, this snippet won't compile and won't authenticate against proxied llama-server setups.

Suggested doc sync
 export class LlamaCppProvider extends BaseProvider {
   private model?: LanguageModel;
   private baseURL: string;
   private apiKey: string;
   private discoveredModel?: string;
+  private readonly requestedModelName?: string;
   private llamaCppClient: ReturnType<typeof createOpenAI>;

   constructor(
     modelName?: string,
     sdk?: unknown,
     _region?: string,
     credentials?: NeurolinkCredentials["llamacpp"],
   ) {
@@
     super(
       modelName,
       "llamacpp" as AIProviderName,
       validatedNeurolink as NeuroLink | undefined,
     );

+    this.requestedModelName = modelName;
     this.baseURL = credentials?.baseURL ?? getLlamaCppBaseURL();
-    this.apiKey = LLAMACPP_PLACEHOLDER_KEY;
+    this.apiKey =
+      credentials?.apiKey ??
+      process.env.LLAMACPP_API_KEY ??
+      LLAMACPP_PLACEHOLDER_KEY;

     this.llamaCppClient = createOpenAI({
       baseURL: this.baseURL,
       apiKey: this.apiKey,
       fetch: createProxyFetch(),
@@
-    const explicit = this.requestedModelName;
+    const explicit = this.requestedModelName;

Based on learnings, lmStudio.ts and llamaCpp.ts use a constructor-captured requestedModelName so fallback mutations of this.modelName do not short-circuit future discovery, and local providers support optional API-key auth for reverse-proxy setups.

Also applies to: 147-157

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

In `@docs/provider-integration/05-llamacpp.md` around lines 85 - 116, The class
snippet fails to declare/capture requestedModelName and always forces
LLAMACPP_PLACEHOLDER_KEY, which breaks model discovery and proxied auth; add a
class property requestedModelName (or ensure existing modelName isn't mutated)
and capture the constructor parameter into this.requestedModelName (match the
pattern used in lmStudio.ts/llamaCpp.ts) so getAISDKModel() can branch
correctly, and stop hardcoding the placeholder by setting this.apiKey =
credentials?.apiKey ?? LLAMACPP_PLACEHOLDER_KEY so reverse-proxy bearer auth is
sent when provided.
docs/provider-integration/09-test-suite-spec.md (1)

165-185: ⚠️ Potential issue | 🟠 Major

This probe snippet is internally inconsistent and won’t run as shown.

It probes each local provider twice, references LM_STUDIO_URL / LLAMACPP_URL before defining them, and keeps the old boolean availability variables next to the newer { available, loadedModel } contract. Please collapse this to one URL/key setup and one probe result per provider.

Suggested doc fix
-const HAS_LM_STUDIO = await probeLocalServer(
-  process.env.TEST_LM_STUDIO_BASE_URL ??
-    process.env.LM_STUDIO_BASE_URL ??
-    "http://localhost:1234/v1",
-);
-const HAS_LLAMACPP = await probeLocalServer(
-  process.env.TEST_LLAMACPP_BASE_URL ??
-    process.env.LLAMACPP_BASE_URL ??
-    "http://localhost:8080/v1",
-);
-
-const LM_STUDIO_KEY =
-  process.env.TEST_LM_STUDIO_API_KEY ?? process.env.LM_STUDIO_API_KEY ?? "";
-const LLAMACPP_KEY =
-  process.env.TEST_LLAMACPP_API_KEY ?? process.env.LLAMACPP_API_KEY ?? "";
+const LM_STUDIO_URL =
+  process.env.TEST_LM_STUDIO_BASE_URL ??
+  process.env.LM_STUDIO_BASE_URL ??
+  "http://localhost:1234/v1";
+const LLAMACPP_URL =
+  process.env.TEST_LLAMACPP_BASE_URL ??
+  process.env.LLAMACPP_BASE_URL ??
+  "http://localhost:8080/v1";
+const LM_STUDIO_KEY =
+  process.env.TEST_LM_STUDIO_API_KEY ?? process.env.LM_STUDIO_API_KEY ?? "";
+const LLAMACPP_KEY =
+  process.env.TEST_LLAMACPP_API_KEY ?? process.env.LLAMACPP_API_KEY ?? "";

 // probeLocalServer returns { available, loadedModel? } — we forward the
 // loadedModel onto each provider entry so the C1 vision-skip predicate can
 // inspect the actual loaded model id instead of guessing.
 const LM_STUDIO_PROBE = await probeLocalServer(LM_STUDIO_URL, LM_STUDIO_KEY);
 const LLAMACPP_PROBE = await probeLocalServer(LLAMACPP_URL, LLAMACPP_KEY);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/provider-integration/09-test-suite-spec.md` around lines 165 - 185, The
snippet is duplicating probes and uses undefined names (LM_STUDIO_URL /
LLAMACPP_URL) and legacy boolean flags (HAS_LM_STUDIO / HAS_LLAMACPP); fix by
collapsing to a single URL/key resolution and a single probe call per provider:
compute LM_STUDIO_URL and LLAMACPP_URL from env vars, compute LM_STUDIO_KEY and
LLAMACPP_KEY, then call probeLocalServer once per provider (e.g.,
LM_STUDIO_PROBE = await probeLocalServer(LM_STUDIO_URL, LM_STUDIO_KEY) and
LLAMACPP_PROBE = await probeLocalServer(LLAMACPP_URL, LLAMACPP_KEY)), remove the
redundant HAS_* boolean variables, and ensure downstream code uses the probe
objects (available, loadedModel) rather than separate booleans.
docs/provider-integration/12-pr-analysis.md (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Option A still documents the superseded pricing plan.

This sample commit message says "symbolic local rates", but the final design in this PR is provider-level _default pricing of 0 for LM Studio / llama.cpp, treated as non-billable. Please update this text so follow-up work doesn't reintroduce the abandoned pricing wording.

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

In `@docs/provider-integration/12-pr-analysis.md` around lines 131 - 135, The docs
still reference "symbolic local rates" which is superseded—update the text in
the summary lines (the bullet that currently reads "Pricing: add _default
sentinel as provider-level fallback + symbolic local rates") to instead state
that pricing uses a provider-level `_default` sentinel set to `0` for LM Studio
and llama.cpp (treated as non-billable) so future contributors don't reintroduce
the old phrasing; ensure the wording mentions the providers (LM Studio /
llama.cpp), the `_default` sentinel, and that `0` means non-billable.
docs/provider-integration/03-nvidia-nim.md (1)

302-338: ⚠️ Potential issue | 🟠 Major

Strip rejected NIM extras from the merged request, not just extraBody.

callStream() re-merges callerBody on every attempt, so removing reasoning_budget or chat_template from extraBody alone still lets caller-supplied copies come back on the retry. The strip needs to happen after mergedBody / mergedKwargs are built.

Based on learnings, in PR #997 the NIM 400 retry path strips reasoning_budget / chat_template from mergedBody / mergedKwargs after the merge step so caller-supplied copies are dropped too.

Also applies to: 376-385

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

In `@docs/provider-integration/03-nvidia-nim.md` around lines 302 - 338, The
merged providerOptions logic currently builds mergedBody and mergedKwargs but
only strips NIM-only fields (like reasoning_budget and
chat_template/chat_template_kwargs) from extraBody, which allows caller-supplied
copies in callerBody to reappear on retries; after computing mergedBody and
mergedKwargs in the providerOptions IIFE (the code that references callerBase,
callerOpenai, callerBody, defaultsBody, mergedBody, mergedKwargs), remove
reasoning_budget from mergedBody and remove chat_template/chat_template_kwargs
keys from mergedKwargs (and delete chat_template from mergedBody if present) so
the final returned openai.body does not include those rejected NIM extras even
if provided by the caller.
src/lib/providers/deepseek.ts (1)

179-185: ⚠️ Potential issue | 🟠 Major

Don't force reasoning on every deepseek-chat request.

This unconditionally injects openai.thinking for all non-deepseek-reasoner calls, so plain chat requests opt into reasoning even when the caller never asked for it. Gate this behind the request's thinking opt-in and leave providerOptions unset otherwise.

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

In `@src/lib/providers/deepseek.ts` around lines 179 - 185, The current
construction unconditionally sets providerOptions with openai.thinking for all
non-isReasoner requests; change this so providerOptions is only set when the
incoming request explicitly opts into thinking (e.g., check the request's
thinking flag/property) and leave providerOptions undefined otherwise. Locate
the providerOptions assignment in deepseek provider creation (the
providerOptions block that uses isReasoner) and change the condition to require
both non-isReasoner and request.thinking (or equivalent request thinking
property) before injecting openai: { thinking: { type: "enabled" } }, otherwise
keep providerOptions undefined.
🧹 Nitpick comments (1)
src/lib/utils/modelChoices.ts (1)

365-375: Handle the empty-string auto-discover option as a real current model.

This keeps value: "" aligned with DEFAULT_MODELS, but getModelChoicesWithDefault() still uses truthiness checks for currentModel. Passing getDefaultModel(AIProviderName.LM_STUDIO) or AIProviderName.LLAMACPP therefore skips the “current model” path entirely, so the auto-discover row is never marked/preselected as current.

Suggested follow-up
-  if (currentModel && !choices.some((c) => c.value === currentModel)) {
+  if (currentModel !== undefined && !choices.some((c) => c.value === currentModel)) {
     // Insert current model at the top if not already in the list
     choices.unshift({
       name: `${currentModel} (current)`,
       value: currentModel,
       description: "Currently configured model",
     });
-  } else if (currentModel) {
+  } else if (currentModel !== undefined) {
     // Mark the current model in the list
     const idx = choices.findIndex((c) => c.value === currentModel);
     if (idx !== -1) {
       choices[idx].name = `${choices[idx].name.replace(/\)$/, ", current)")}`;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/utils/modelChoices.ts` around lines 365 - 375, The current-model
selection logic treats an empty-string model as falsy so the auto-discover entry
(value: "") never gets marked current; update getModelChoicesWithDefault to
check currentModel explicitly for undefined/null (e.g., currentModel !==
undefined && currentModel !== null) or use a defined-ness helper instead of a
truthiness check, and ensure the selection comparison uses strict equality
(choice.value === currentModel) so value: "" will be recognized when
getDefaultModel(AIProviderName.LM_STUDIO) or AIProviderName.LLAMACPP returns "".
Locate and change the conditional in getModelChoicesWithDefault (and any helpers
that gate the “current model” path) to use explicit null/undefined checks rather
than if (currentModel) truthiness.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/provider-integration/01-shared-changes.md`:
- Around line 9-25: The docs suggest adding new entries to the AIProviderName
enum in the wrong canonical file; update the actual AIProviderName enum
definition in providers.ts (the canonical types file) to include the four new
provider names: DEEPSEEK, NVIDIA_NIM, LM_STUDIO, and LLAMACPP (with appropriate
string values like "deepseek", "nvidia-nim", "lm-studio", "llamacpp") placed
before the existing AUTO entry so the enum reflects the new providers.

In `@docs/provider-integration/02-deepseek.md`:
- Around line 157-162: The current branch only checks modelName (isReasoner) and
always injects providerOptions.openai.thinking for non-reasoner models; change
it to also check the incoming request's thinking flag (e.g., thinking_enabled or
a request.thinking boolean) so that providerOptions.openai = { thinking: { type:
"enabled" } } is set only when both the model is not
DeepSeekModels.DEEPSEEK_REASONER and the request explicitly requests thinking.
Locate the block using isReasoner, DeepSeekModels.DEEPSEEK_REASONER,
providerOptions and modelName and add the additional guard against the request's
thinking flag to mirror the thinking_enabled behavior described in the source
notes.

In `@docs/provider-integration/04-lm-studio.md`:
- Around line 87-99: The class LMStudioProvider is missing a declared field for
requestedModelName which getAISDKModel() expects; add a readonly field (e.g.,
readonly requestedModelName?: string) to the class and initialize it from the
constructor parameter (capture the incoming modelName into
this.requestedModelName) so explicit-vs-auto-discovery is preserved and fallback
writes to this.modelName do not short-circuit future /v1/models retries; apply
the same pattern used in lmStudio.ts/llamaCpp.ts and ensure references in
getAISDKModel, modelName, and any fallback logic (also update the analogous
snippet at the other occurrence around lines 163-170) use
this.requestedModelName.

In `@docs/provider-integration/10-test-results-final.md`:
- Around line 97-104: Update the “final” failure list so it’s consistent with
the investigation fixes: either mark the RAGAS Context Precision (Test 4), Abort
Signal Stream, and Cross-provider Observability Spans entries as “historical
snapshot” (with a short note linking to the companion investigation) or refresh
the table and bottom-line watchlist to remove or change the status of those
tests to “fixed/root cause identified”; ensure the specific rows referencing
"RAGAS Context Precision (Test 4)", "Abort Signal Stream", and "Cross-provider
Observability Spans" (and the similar section around the 181-190 mention)
reflect the corrected state so the PR does not present conflicting final
results.

In `@src/lib/providers/deepseek.ts`:
- Around line 103-107: The DeepSeek provider currently accepts blank/whitespace
overrides because the constructor assigns this.apiKey from credentials?.apiKey
without validating; change the logic in the DeepSeek constructor (the code
around this.apiKey and use of credentials?.apiKey) to trim credentials.apiKey
first, then if the trimmed string is non-empty use it, otherwise call
getDeepSeekApiKey() for a validated env key and assign that, and if neither
yields a usable key throw a clear error; keep the same precedence but reject
empty/whitespace overrides by trimming before deciding.

In `@src/lib/providers/llamaCpp.ts`:
- Around line 209-212: The model discovery call await this.getAISDKModel()
currently runs before the request timeout/abort controller is created so
options.abortSignal and short timeouts are ignored; either move the code that
creates the per-request timeout/abort controller (the logic that creates the
request timeout controller and sets options.abortSignal) to run before calling
getAISDKModel(), or modify getAISDKModel() to accept and honor a passed
AbortSignal/timeout (e.g., add a parameter like signal or options.signal and
propagate it into the internal 5s probe) and pass options.abortSignal into it;
update any span-opening logic to still occur after discovery if needed but
ensure the abort signal is wired into discovery so cancellation and short
timeouts are respected.

In `@src/lib/providers/lmStudio.ts`:
- Around line 217-220: The model discovery call await this.getAISDKModel() runs
before the per-request timeout/abort controller is created and
getAvailableModels() uses its own 5s timeout, so per-request options.abortSignal
and short timeouts are ignored; fix by threading the request's abort/timeout
into discovery: either create the per-request timeout/abort controller before
calling getAISDKModel() and pass that controller.signal into getAISDKModel(), or
modify getAISDKModel() to accept an optional AbortSignal and forward it into
getAvailableModels() (and into its internal timeout logic) so
options.abortSignal and short request timeouts are honored during model
discovery.
- Around line 368-379: validateConfiguration currently only checks r.ok for GET
`${this.baseURL.replace(/\/$/, "")}/models` which returns true even when no
models are loaded; change validateConfiguration (in the lmStudio provider) to
fetch and parse the JSON body (use the same proxyFetch and headers logic),
verify the payload contains at least one model entry (e.g., non-empty array or
expected model field) and return true only when r.ok AND the models list is
non-empty; keep the same timeout and error handling and make sure this aligns
with getAISDKModel's expectations so providers with no loaded model return
false.

In `@src/lib/providers/nvidiaNim.ts`:
- Around line 187-191: Normalize and validate credential overrides before
applying precedence: if credentials?.apiKey is present, trim it and if the
trimmed string is empty throw an error (reject explicit blank overrides);
otherwise use the trimmed value for this.apiKey; if credentials?.apiKey is
undefined use getNimApiKey() as the fallback. Update the assignment logic around
this.apiKey (and reference credentials?.apiKey and getNimApiKey) to perform this
normalization/validation rather than using the nullish-coalescing directly.

In `@test/continuous-test-suite-memory.ts`:
- Around line 45-50: The comment above the provider caps is misleading; change
the generic “small context-window models, so cap output reserve at 1024” line to
call out provider-specific reserves and values for deepseek (4096), "nvidia-nim"
(8192), "lm-studio" (1024) and llamacpp (1024) so the comment matches the
constants; update the comment to list each provider and its output-reserve cap
(or say “varies by provider — see per-provider values below”) and ensure the
entries for deepseek, "nvidia-nim", lm-studio and llamacpp remain unchanged.

In `@test/continuous-test-suite-new-providers.ts`:
- Around line 716-740: The E2 "thinking.minimal" test uses p.reasoningModel
(e.g., deepseek-reasoner) which may always return reasoning and causes false
failures; modify the runProviderTest invocation (the async callback passed into
runProviderTest for "E2 thinking.minimal") to either use a toggleable/chat model
instead of p.reasoningModel (e.g., p.chatModel or a new p.toggleableModel) when
available, or skip/mark the test for providers that declare an always-on
reasoning model (add/consult a flag like p.reasoningAlwaysOn or
p.supportsMinimalThinking) so the assertion that res.reasoning and
res.analytics.reasoning are empty only runs for providers whose model can be
configured to minimal reasoning.

---

Outside diff comments:
In `@src/lib/adapters/providerImageAdapter.ts`:
- Around line 57-76: getSupportedModels is still using provider.toLowerCase() to
index VISION_CAPABILITIES while supportsVision uses normalizeVisionProvider,
causing inconsistent behavior for aliases; update getSupportedModels to call
normalizeVisionProvider(provider) (rather than provider.toLowerCase()) before
looking up VISION_CAPABILITIES so both helpers canonicalize aliases the same
way, and ensure the returned key matches the expectations used in
normalizeVisionProvider (e.g., "lm-studio", "nvidia-nim", "llamacpp",
"google-ai", "openrouter").

In `@src/lib/proxy/proxyFetch.ts`:
- Around line 772-786: maskProxyUrl currently returns the full URL including
query string which can leak signed tokens; modify maskProxyUrl to redact query
parameters before returning by parsing the URL (const u = new URL(url)),
replacing username/password as already done, then clear or sanitize the query
portion (e.g., set u.search = "" or iterate u.searchParams and replace each
value with "***") so no raw query values are returned, and finally return
u.toString(); keep the existing try/catch and invalid-url behavior.

In `@test/continuous-test-suite-credentials.ts`:
- Around line 545-568: The test references an undefined identifier
availableLower in the assertion; replace that with the already-defined
availableNormalized (or available if you intended original values) so the
assertion shows correct provider names. Update the assertion message to use
availableNormalized.join(", ") (keeping the existing matchCount check), and
verify this uses the normalization function norm and variables credKeys,
available, and matchCount used above.

---

Duplicate comments:
In `@docs/provider-integration/03-nvidia-nim.md`:
- Around line 302-338: The merged providerOptions logic currently builds
mergedBody and mergedKwargs but only strips NIM-only fields (like
reasoning_budget and chat_template/chat_template_kwargs) from extraBody, which
allows caller-supplied copies in callerBody to reappear on retries; after
computing mergedBody and mergedKwargs in the providerOptions IIFE (the code that
references callerBase, callerOpenai, callerBody, defaultsBody, mergedBody,
mergedKwargs), remove reasoning_budget from mergedBody and remove
chat_template/chat_template_kwargs keys from mergedKwargs (and delete
chat_template from mergedBody if present) so the final returned openai.body does
not include those rejected NIM extras even if provided by the caller.

In `@docs/provider-integration/05-llamacpp.md`:
- Around line 85-116: The class snippet fails to declare/capture
requestedModelName and always forces LLAMACPP_PLACEHOLDER_KEY, which breaks
model discovery and proxied auth; add a class property requestedModelName (or
ensure existing modelName isn't mutated) and capture the constructor parameter
into this.requestedModelName (match the pattern used in lmStudio.ts/llamaCpp.ts)
so getAISDKModel() can branch correctly, and stop hardcoding the placeholder by
setting this.apiKey = credentials?.apiKey ?? LLAMACPP_PLACEHOLDER_KEY so
reverse-proxy bearer auth is sent when provided.

In `@docs/provider-integration/06-testing.md`:
- Around line 67-83: The tests use the old assertions and skip behavior: in the
runTest block creating new NeuroLink() and calling generate({ provider:
"nvidia-nim", ... }) change the skip path to explicitly return null (not bare
return) and update success checks to assert typeof result?.content === "string"
instead of result?.text; also normalize provider error messages with
toLowerCase() before matching (e.g., when detecting provider-specific errors) to
make detection case-insensitive. Apply the same changes to the other NVIDIA NIM
and LM Studio examples mentioned (the blocks around lines 85-106 and 143-159).

In `@docs/provider-integration/07-implementation-order.md`:
- Around line 90-104: Add a blank line before and after each fenced code block
in Milestone 4 so Markdown lint rule MD031 is satisfied; specifically, insert an
empty line above and below the three triple-backtick bash blocks that start with
"export NVIDIA_NIM_API_KEY..." (base case), the retry-on-400 block beginning
with "# Use a model that doesn't support reasoning_budget", and the vision model
block starting with "pnpm run cli generate... --image ./test.jpg" to ensure the
fences are surrounded by blank lines.

In `@docs/provider-integration/09-test-suite-spec.md`:
- Around line 165-185: The snippet is duplicating probes and uses undefined
names (LM_STUDIO_URL / LLAMACPP_URL) and legacy boolean flags (HAS_LM_STUDIO /
HAS_LLAMACPP); fix by collapsing to a single URL/key resolution and a single
probe call per provider: compute LM_STUDIO_URL and LLAMACPP_URL from env vars,
compute LM_STUDIO_KEY and LLAMACPP_KEY, then call probeLocalServer once per
provider (e.g., LM_STUDIO_PROBE = await probeLocalServer(LM_STUDIO_URL,
LM_STUDIO_KEY) and LLAMACPP_PROBE = await probeLocalServer(LLAMACPP_URL,
LLAMACPP_KEY)), remove the redundant HAS_* boolean variables, and ensure
downstream code uses the probe objects (available, loadedModel) rather than
separate booleans.

In `@docs/provider-integration/12-pr-analysis.md`:
- Around line 131-135: The docs still reference "symbolic local rates" which is
superseded—update the text in the summary lines (the bullet that currently reads
"Pricing: add _default sentinel as provider-level fallback + symbolic local
rates") to instead state that pricing uses a provider-level `_default` sentinel
set to `0` for LM Studio and llama.cpp (treated as non-billable) so future
contributors don't reintroduce the old phrasing; ensure the wording mentions the
providers (LM Studio / llama.cpp), the `_default` sentinel, and that `0` means
non-billable.

In `@src/cli/factories/commandFactory.ts`:
- Around line 80-89: The CLI's yargs provider choices and bash completion list
in src/cli/factories/commandFactory.ts are out of sync with ProviderRegistry:
add the missing provider aliases ("llama-cpp", and also include "nvidia" and
"lms" where appropriate) to the yargs choices array (the list currently
containing "nvidia", "lms", "llamacpp", "llama.cpp") and to the
bash/tab-completion generation so those aliases are accepted at parse time and
show up in completion; update any variables or constants used for provider
choices/completion generation in commandFactory.ts to include "llama-cpp",
"nvidia", and "lms" to match ProviderRegistry.

In `@src/lib/constants/contextWindows.ts`:
- Around line 273-275: PROVIDER_ALIAS_MAP currently maps "googleaistudio" ->
"google-ai-studio" but the context windows registry uses the key "google-ai", so
getContextWindowSize("google-ai-studio", ...) falls back to
DEFAULT_CONTEXT_WINDOW; update the mapping in PROVIDER_ALIAS_MAP to map
"googleaistudio" -> "google-ai" (and similarly fix any other alias entries that
point to "-studio" instead of the registry key), and ensure getContextWindowSize
is using PROVIDER_ALIAS_MAP to normalize provider names before looking up the
context window sizes.

In `@src/lib/providers/deepseek.ts`:
- Around line 179-185: The current construction unconditionally sets
providerOptions with openai.thinking for all non-isReasoner requests; change
this so providerOptions is only set when the incoming request explicitly opts
into thinking (e.g., check the request's thinking flag/property) and leave
providerOptions undefined otherwise. Locate the providerOptions assignment in
deepseek provider creation (the providerOptions block that uses isReasoner) and
change the condition to require both non-isReasoner and request.thinking (or
equivalent request thinking property) before injecting openai: { thinking: {
type: "enabled" } }, otherwise keep providerOptions undefined.

In `@test/continuous-test-suite-evaluation.ts`:
- Around line 1607-1620: The alias map PROVIDER_KEY_ALIASES is missing the CLI
shorthands for DeepSeek and NVIDIA, so add entries mapping "ds" to the DeepSeek
canonical key (e.g., "deepseek") and "nvidia" to "nvidia-nim" inside the
PROVIDER_KEY_ALIASES object so the canonical lookup used to set canonical (and
subsequently TEST_CONFIG.maxTokens) resolves to the correct provider budgets
instead of falling back to 1024.

In `@test/run-provider-matrix.sh`:
- Line 146: The loop currently hardcodes "context memory" after $SUITES (for
suite in $SUITES context memory;), which causes the matrix renderer to output
rows that are never executed; either remove those synthetic literals from the
loop or ensure they are part of $SUITES and actually executed. Fix by updating
the iteration to only use $SUITES (for suite in $SUITES;) or by appending
"context" and "memory" to the SUITES variable earlier (e.g. export
SUITES="$SUITES context memory") so the runner and matrix stay in sync; target
the loop that iterates over suites and the code that sets/exports SUITES to make
the change.

---

Nitpick comments:
In `@src/lib/utils/modelChoices.ts`:
- Around line 365-375: The current-model selection logic treats an empty-string
model as falsy so the auto-discover entry (value: "") never gets marked current;
update getModelChoicesWithDefault to check currentModel explicitly for
undefined/null (e.g., currentModel !== undefined && currentModel !== null) or
use a defined-ness helper instead of a truthiness check, and ensure the
selection comparison uses strict equality (choice.value === currentModel) so
value: "" will be recognized when getDefaultModel(AIProviderName.LM_STUDIO) or
AIProviderName.LLAMACPP returns "". Locate and change the conditional in
getModelChoicesWithDefault (and any helpers that gate the “current model” path)
to use explicit null/undefined checks rather than if (currentModel) truthiness.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11b7061b-d2b1-4ddb-ae37-e782704cefa9

📥 Commits

Reviewing files that changed from the base of the PR and between f0cb633 and 8c6ac5a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (51)
  • .env.example
  • docs/provider-integration/00-architecture.md
  • docs/provider-integration/01-shared-changes.md
  • docs/provider-integration/02-deepseek.md
  • docs/provider-integration/03-nvidia-nim.md
  • docs/provider-integration/04-lm-studio.md
  • docs/provider-integration/05-llamacpp.md
  • docs/provider-integration/06-testing.md
  • docs/provider-integration/07-implementation-order.md
  • docs/provider-integration/08-feature-matrix.md
  • docs/provider-integration/09-test-suite-spec.md
  • docs/provider-integration/10-test-results-final.md
  • docs/provider-integration/11-test-failure-investigation.md
  • docs/provider-integration/12-pr-analysis.md
  • docs/provider-integration/13-code-review.md
  • docs/provider-integration/README.md
  • package.json
  • src/cli/factories/commandFactory.ts
  • src/lib/adapters/providerImageAdapter.ts
  • src/lib/constants/contextWindows.ts
  • src/lib/constants/enums.ts
  • src/lib/core/baseProvider.ts
  • src/lib/factories/providerRegistry.ts
  • src/lib/providers/deepseek.ts
  • src/lib/providers/index.ts
  • src/lib/providers/llamaCpp.ts
  • src/lib/providers/lmStudio.ts
  • src/lib/providers/nvidiaNim.ts
  • src/lib/proxy/proxyFetch.ts
  • src/lib/types/providers.ts
  • src/lib/utils/modelChoices.ts
  • src/lib/utils/pricing.ts
  • src/lib/utils/providerConfig.ts
  • test/continuous-test-suite-client.ts
  • test/continuous-test-suite-context.ts
  • test/continuous-test-suite-credentials.ts
  • test/continuous-test-suite-evaluation-scoring.ts
  • test/continuous-test-suite-evaluation.ts
  • test/continuous-test-suite-mcp-http.ts
  • test/continuous-test-suite-mcp.ts
  • test/continuous-test-suite-media-gen.ts
  • test/continuous-test-suite-memory.ts
  • test/continuous-test-suite-new-providers.ts
  • test/continuous-test-suite-observability.ts
  • test/continuous-test-suite-ppt.ts
  • test/continuous-test-suite-providers.ts
  • test/continuous-test-suite-session-memory-bugs.ts
  • test/continuous-test-suite-tts.ts
  • test/continuous-test-suite-workflow.ts
  • test/continuous-test-suite.ts
  • test/run-provider-matrix.sh

Comment on lines +9 to +25
## §1. `src/lib/constants/enums.ts`

### 1a. Extend `AIProviderName` enum (line 8)

Add four entries before `AUTO`:

```ts
export enum AIProviderName {
// ... existing entries ...
SAGEMAKER = "sagemaker",
+ DEEPSEEK = "deepseek",
+ NVIDIA_NIM = "nvidia-nim",
+ LM_STUDIO = "lm-studio",
+ LLAMACPP = "llamacpp",
AUTO = "auto",
}
```

ghost Apr 29, 2026

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

Point AIProviderName edits at the canonical file.

This section tells contributors to extend AIProviderName in src/lib/constants/enums.ts, but the enum’s canonical home in this repo is src/lib/types/providers.ts. Leaving this as-is will send follow-up work to the wrong file and make this “touch once” guide unreliable.

Based on learnings: Add new provider names to AIProviderName enum in src/lib/types/providers.ts.

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

In `@docs/provider-integration/01-shared-changes.md` around lines 9 - 25, The docs
suggest adding new entries to the AIProviderName enum in the wrong canonical
file; update the actual AIProviderName enum definition in providers.ts (the
canonical types file) to include the four new provider names: DEEPSEEK,
NVIDIA_NIM, LM_STUDIO, and LLAMACPP (with appropriate string values like
"deepseek", "nvidia-nim", "lm-studio", "llamacpp") placed before the existing
AUTO entry so the enum reflects the new providers.

Comment on lines +157 to +162
// For deepseek-chat (not the reasoner), opt into thinking via extra body
const isReasoner = this.modelName === DeepSeekModels.DEEPSEEK_REASONER;
const providerOptions: Record<string, unknown> = {};
if (!isReasoner) {
providerOptions.openai = { thinking: { type: "enabled" } };
}

ghost Apr 29, 2026

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 | 🟠 Major

Don't force thinking on every deepseek-chat request.

This branch keys only on the model, so normal deepseek-chat calls always send thinking: { type: "enabled" }. That changes the default latency/cost profile and removes any minimal/no-thinking path. Gate this on the request's thinking flag as well, mirroring the thinking_enabled behavior called out in the source-repo notes.

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

In `@docs/provider-integration/02-deepseek.md` around lines 157 - 162, The current
branch only checks modelName (isReasoner) and always injects
providerOptions.openai.thinking for non-reasoner models; change it to also check
the incoming request's thinking flag (e.g., thinking_enabled or a
request.thinking boolean) so that providerOptions.openai = { thinking: { type:
"enabled" } } is set only when both the model is not
DeepSeekModels.DEEPSEEK_REASONER and the request explicitly requests thinking.
Locate the block using isReasoner, DeepSeekModels.DEEPSEEK_REASONER,
providerOptions and modelName and add the additional guard against the request's
thinking flag to mirror the thinking_enabled behavior described in the source
notes.

Comment on lines +87 to +99
export class LMStudioProvider extends BaseProvider {
private model?: LanguageModel;
private baseURL: string;
private apiKey: string;
private discoveredModel?: string;
private lmstudioClient: ReturnType<typeof createOpenAI>;

constructor(
modelName?: string,
sdk?: unknown,
_region?: string,
credentials?: NeurolinkCredentials["lmStudio"],
) {

ghost Apr 29, 2026

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

Declare and initialize requestedModelName in this spec snippet.

getAISDKModel() relies on this.requestedModelName, but the class fields and constructor here never define it. As written, the example doesn't type-check and doesn't actually show how the explicit-vs-auto-discovery split is preserved. Add a readonly field and assign it from the constructor input.

Based on learnings, lmStudio.ts / llamaCpp.ts use a constructor-captured requestedModelName so fallback writes to this.modelName do not short-circuit future /v1/models retries.

Also applies to: 163-170

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

In `@docs/provider-integration/04-lm-studio.md` around lines 87 - 99, The class
LMStudioProvider is missing a declared field for requestedModelName which
getAISDKModel() expects; add a readonly field (e.g., readonly
requestedModelName?: string) to the class and initialize it from the constructor
parameter (capture the incoming modelName into this.requestedModelName) so
explicit-vs-auto-discovery is preserved and fallback writes to this.modelName do
not short-circuit future /v1/models retries; apply the same pattern used in
lmStudio.ts/llamaCpp.ts and ensure references in getAISDKModel, modelName, and
any fallback logic (also update the analogous snippet at the other occurrence
around lines 163-170) use this.requestedModelName.

Comment on lines +97 to +104
Sub-test fail breakdown (the user's target was 100%, here's the gap):

| Failing test | Provider(s) | Why |
| ----------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| RAGAS Context Precision (Test 4) | NVIDIA NIM | Llama 3.3 70B as judge gives 1.00 to both focused and bloated contexts. Real model behavior. Fix: use stronger judge model. |
| Memory with Large Context (Test 12) | lm-studio | 1 sub-test failure with local model — needs investigation. |
| Abort Signal Stream | lm-studio, llamacpp | Specific test of stream abort with local models. |
| Cross-provider Observability Spans | deepseek, lm-studio (via providers cross-loop) | This test runs across ALL providers and fails on Vertex/Anthropic/etc due to env. |

ghost Apr 29, 2026

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

This “final” failure list is stale relative to the fixes documented elsewhere in the PR.

This section still treats RAGAS Context Precision, Abort Signal Stream, and cross-provider observability as open gaps, but the companion investigation page records those as fixed/root-caused already. Please either relabel this as a historical snapshot or refresh the breakdown and bottom-line watchlist so the PR doesn't ship two different “final” states.

Also applies to: 181-190

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

In `@docs/provider-integration/10-test-results-final.md` around lines 97 - 104,
Update the “final” failure list so it’s consistent with the investigation fixes:
either mark the RAGAS Context Precision (Test 4), Abort Signal Stream, and
Cross-provider Observability Spans entries as “historical snapshot” (with a
short note linking to the companion investigation) or refresh the table and
bottom-line watchlist to remove or change the status of those tests to
“fixed/root cause identified”; ensure the specific rows referencing "RAGAS
Context Precision (Test 4)", "Abort Signal Stream", and "Cross-provider
Observability Spans" (and the similar section around the 181-190 mention)
reflect the corrected state so the PR does not present conflicting final
results.

Comment thread src/lib/providers/deepseek.ts Outdated
Comment on lines +103 to +107
this.apiKey = credentials?.apiKey ?? getDeepSeekApiKey();
this.baseURL =
credentials?.baseURL ??
process.env.DEEPSEEK_BASE_URL ??
DEEPSEEK_DEFAULT_BASE_URL;

ghost Apr 29, 2026

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 | 🟠 Major

Reject blank DeepSeek API key overrides.

This has the same precedence bug as the NIM constructor: "" or whitespace in credentials.apiKey bypasses getDeepSeekApiKey() and builds a client with an unusable credential. Trim the override first, then either use it, fall back to the validated env key, or throw.

Suggested fix
-    this.apiKey = credentials?.apiKey ?? getDeepSeekApiKey();
+    const overrideApiKey = credentials?.apiKey?.trim();
+    this.apiKey =
+      overrideApiKey && overrideApiKey.length > 0
+        ? overrideApiKey
+        : getDeepSeekApiKey();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.apiKey = credentials?.apiKey ?? getDeepSeekApiKey();
this.baseURL =
credentials?.baseURL ??
process.env.DEEPSEEK_BASE_URL ??
DEEPSEEK_DEFAULT_BASE_URL;
const overrideApiKey = credentials?.apiKey?.trim();
this.apiKey =
overrideApiKey && overrideApiKey.length > 0
? overrideApiKey
: getDeepSeekApiKey();
this.baseURL =
credentials?.baseURL ??
process.env.DEEPSEEK_BASE_URL ??
DEEPSEEK_DEFAULT_BASE_URL;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/providers/deepseek.ts` around lines 103 - 107, The DeepSeek provider
currently accepts blank/whitespace overrides because the constructor assigns
this.apiKey from credentials?.apiKey without validating; change the logic in the
DeepSeek constructor (the code around this.apiKey and use of
credentials?.apiKey) to trim credentials.apiKey first, then if the trimmed
string is non-empty use it, otherwise call getDeepSeekApiKey() for a validated
env key and assign that, and if neither yields a usable key throw a clear error;
keep the same precedence but reject empty/whitespace overrides by trimming
before deciding.

Comment thread src/lib/providers/lmStudio.ts Outdated
Comment on lines +217 to +220
// Resolve the LM Studio model BEFORE opening the span so OTEL
// attributes, MessageBuilder, and downstream image/tool adapters all see
// the discovered model id rather than the empty pre-discovery placeholder.
await this.getAISDKModel();

ghost Apr 29, 2026

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 | 🟠 Major

Model discovery here bypasses request cancellation.

This warm-up runs before the request timeout controller exists, and getAvailableModels() uses its own fixed 5s timeout, so options.abortSignal and short per-request timeouts are ignored during discovery. Please thread the request signal/timeout into discovery, or create the timeout controller before this call.

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

In `@src/lib/providers/lmStudio.ts` around lines 217 - 220, The model discovery
call await this.getAISDKModel() runs before the per-request timeout/abort
controller is created and getAvailableModels() uses its own 5s timeout, so
per-request options.abortSignal and short timeouts are ignored; fix by threading
the request's abort/timeout into discovery: either create the per-request
timeout/abort controller before calling getAISDKModel() and pass that
controller.signal into getAISDKModel(), or modify getAISDKModel() to accept an
optional AbortSignal and forward it into getAvailableModels() (and into its
internal timeout logic) so options.abortSignal and short request timeouts are
honored during model discovery.

Comment thread src/lib/providers/lmStudio.ts Outdated
Comment on lines +368 to +379
async validateConfiguration(): Promise<boolean> {
try {
const url = `${this.baseURL.replace(/\/$/, "")}/models`;
const proxyFetch = createProxyFetch();
const r = await proxyFetch(url, {
headers:
this.apiKey && this.apiKey !== LM_STUDIO_PLACEHOLDER_KEY
? { Authorization: `Bearer ${this.apiKey}` }
: undefined,
signal: AbortSignal.timeout(5000),
});
return r.ok;

ghost Apr 29, 2026

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 | 🟠 Major

Require a loaded model in validateConfiguration().

A 200 from /models is not sufficient here. When the payload is empty, getAISDKModel() treats LM Studio as unusable and falls back to local-model, but this method still returns true, so health/config checks report the provider as available with no loaded model.

Suggested fix
   async validateConfiguration(): Promise<boolean> {
     try {
       const url = `${this.baseURL.replace(/\/$/, "")}/models`;
       const proxyFetch = createProxyFetch();
       const r = await proxyFetch(url, {
         headers:
           this.apiKey && this.apiKey !== LM_STUDIO_PLACEHOLDER_KEY
             ? { Authorization: `Bearer ${this.apiKey}` }
             : undefined,
         signal: AbortSignal.timeout(5000),
       });
-      return r.ok;
+      if (!r.ok) {
+        return false;
+      }
+      const data = (await r.json()) as ModelsResponse;
+      return data.data.some(
+        (model) => typeof model.id === "string" && model.id.trim() !== "",
+      );
     } catch {
       return false;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/providers/lmStudio.ts` around lines 368 - 379, validateConfiguration
currently only checks r.ok for GET `${this.baseURL.replace(/\/$/, "")}/models`
which returns true even when no models are loaded; change validateConfiguration
(in the lmStudio provider) to fetch and parse the JSON body (use the same
proxyFetch and headers logic), verify the payload contains at least one model
entry (e.g., non-empty array or expected model field) and return true only when
r.ok AND the models list is non-empty; keep the same timeout and error handling
and make sure this aligns with getAISDKModel's expectations so providers with no
loaded model return false.

Comment thread src/lib/providers/nvidiaNim.ts Outdated
Comment on lines +187 to +191
this.apiKey = credentials?.apiKey ?? getNimApiKey();
this.baseURL =
credentials?.baseURL ??
process.env.NVIDIA_NIM_BASE_URL ??
NVIDIA_NIM_DEFAULT_BASE_URL;

ghost Apr 29, 2026

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 | 🟠 Major

Reject blank NVIDIA NIM API key overrides.

credentials?.apiKey ?? getNimApiKey() treats "" and whitespace as explicit overrides, so a caller can bypass env validation and instantiate the provider with an unusable bearer token. Normalize the override before applying precedence, and either fall back to the validated env key or throw for blank cloud-provider keys.

Suggested fix
-    this.apiKey = credentials?.apiKey ?? getNimApiKey();
+    const overrideApiKey = credentials?.apiKey?.trim();
+    this.apiKey =
+      overrideApiKey && overrideApiKey.length > 0
+        ? overrideApiKey
+        : getNimApiKey();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/providers/nvidiaNim.ts` around lines 187 - 191, Normalize and
validate credential overrides before applying precedence: if credentials?.apiKey
is present, trim it and if the trimmed string is empty throw an error (reject
explicit blank overrides); otherwise use the trimmed value for this.apiKey; if
credentials?.apiKey is undefined use getNimApiKey() as the fallback. Update the
assignment logic around this.apiKey (and reference credentials?.apiKey and
getNimApiKey) to perform this normalization/validation rather than using the
nullish-coalescing directly.

Comment thread test/continuous-test-suite-memory.ts Outdated
Comment on lines +45 to +50
// OpenAI-compat providers added 2026 — small context-window models, so cap
// output reserve at 1024 to leave room for the multi-turn prompt + tool defs.
deepseek: 4096,
"nvidia-nim": 8192,
"lm-studio": 1024,
llamacpp: 1024,

ghost Apr 29, 2026

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

Comment no longer matches the caps below it.

The note says the reserve is capped at 1024, but deepseek and "nvidia-nim" are set to 4096 and 8192. Please make the wording provider-specific so the next edit does not optimize around the wrong limit.

✏️ Suggested wording
-  // OpenAI-compat providers added 2026 — small context-window models, so cap
-  // output reserve at 1024 to leave room for the multi-turn prompt + tool defs.
+  // OpenAI-compat providers added 2026. Use provider-specific caps to leave
+  // room for the multi-turn prompt + tool definitions.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// OpenAI-compat providers added 2026 — small context-window models, so cap
// output reserve at 1024 to leave room for the multi-turn prompt + tool defs.
deepseek: 4096,
"nvidia-nim": 8192,
"lm-studio": 1024,
llamacpp: 1024,
// OpenAI-compat providers added 2026. Use provider-specific caps to leave
// room for the multi-turn prompt + tool definitions.
deepseek: 4096,
"nvidia-nim": 8192,
"lm-studio": 1024,
llamacpp: 1024,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/continuous-test-suite-memory.ts` around lines 45 - 50, The comment above
the provider caps is misleading; change the generic “small context-window
models, so cap output reserve at 1024” line to call out provider-specific
reserves and values for deepseek (4096), "nvidia-nim" (8192), "lm-studio" (1024)
and llamacpp (1024) so the comment matches the constants; update the comment to
list each provider and its output-reserve cap (or say “varies by provider — see
per-provider values below”) and ensure the entries for deepseek, "nvidia-nim",
lm-studio and llamacpp remain unchanged.

Comment on lines +716 to +740
await runProviderTest("E2 thinking.minimal", p, async (signal) => {
const sdk = makeSdk();
const res = await sdk.generate({
input: { text: "What is 2+2?" },
provider: p.name,
abortSignal: signal,
model: p.reasoningModel,
thinkingLevel: "minimal",
maxTokens: 64,
});
// Require normal content while reasoning is empty/absent — proves
// `thinkingLevel: "minimal"` is honored, not silently treated as "high".
// Mirror E1: also inspect `analytics.reasoning` so providers that route
// reasoning through analytics can't pass the minimal test by leaving
// `res.reasoning` empty.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const r = res as any;
const isReasoningEmpty = (val: unknown): boolean =>
!val ||
(typeof val === "string" && val.length === 0) ||
(Array.isArray(val) && val.length === 0);
const reasoningEmpty =
isReasoningEmpty(r?.reasoning) &&
isReasoningEmpty(r?.analytics?.reasoning);
return Boolean(res?.content) && reasoningEmpty;

ghost Apr 29, 2026

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 | 🟠 Major

E2 uses the wrong model to verify "minimal" reasoning.

This test runs against p.reasoningModel, but for DeepSeek that is deepseek-reasoner, which the provider docs in this PR describe as always reasoning. Requiring reasoningEmpty here can turn a valid response into a false failure. Use a toggleable chat model for the minimal-path assertion, or skip E2 for providers whose reasoning model is inherently always-on.

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

In `@test/continuous-test-suite-new-providers.ts` around lines 716 - 740, The E2
"thinking.minimal" test uses p.reasoningModel (e.g., deepseek-reasoner) which
may always return reasoning and causes false failures; modify the
runProviderTest invocation (the async callback passed into runProviderTest for
"E2 thinking.minimal") to either use a toggleable/chat model instead of
p.reasoningModel (e.g., p.chatModel or a new p.toggleableModel) when available,
or skip/mark the test for providers that declare an always-on reasoning model
(add/consult a flag like p.reasoningAlwaysOn or p.supportsMinimalThinking) so
the assertion that res.reasoning and res.analytics.reasoning are empty only runs
for providers whose model can be configured to minimal reasoning.

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

ghost commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

Add four OpenAI-compatible providers via the AI SDK's createOpenAI().chat()
endpoint, each with full BaseProvider semantics, OTEL tracing, error
formatting, and validateConfiguration support.

Providers:
- DeepSeek (api.deepseek.com): cloud, requires DEEPSEEK_API_KEY.
- NVIDIA NIM (integrate.api.nvidia.com): cloud, requires NVIDIA_NIM_API_KEY,
  with NIM-specific extras (top_k, min_p, repetition_penalty, reasoning_budget,
  chat_template) passed via providerOptions.openai.body and a retry-on-400
  path that strips unsupported fields.
- LM Studio (localhost:1234): local, auto-discovers loaded model from
  /v1/models. Optional LM_STUDIO_API_KEY for users behind an auth proxy.
- llama.cpp (localhost:8080): local, auto-discovers loaded model. /health
  validation has 3x retry. Optional LLAMACPP_API_KEY for proxied setups.

Credential pass-through verified end-to-end: per-call options.credentials >
NeuroLink instance credentials > env vars > documented defaults.

Pricing changes (src/lib/utils/pricing.ts):
- Add 4 provider entries with symbolic local rates ($1/M tokens) so cost
  attribution reports a non-zero value after the 6-decimal rounding step.
- Treat the `_default` map key as a provider-level fallback (filtered from
  prefix matches, used as last resort) so providers that don't enumerate
  per-model pricing still get a non-undefined rate.

Context-window root-cause fix (src/lib/constants/contextWindows.ts):
- Clamp getOutputReserve to 80% of the context window. Previously, callers
  that passed maxTokens === contextWindow (or larger) got a 0-token input
  budget and every request failed with "Budget: 0 tokens" before being sent.
  Affected 12 test suites and any user passing an oversized maxTokens.

BaseProvider change (src/lib/core/baseProvider.ts):
- Drop `readonly` on `modelName` so providers that auto-discover the model
  via /v1/models (lm-studio, llamacpp) can update it after construction,
  ensuring TelemetryHandler/MessageBuilder cache the resolved name and
  result.model is never empty.

Test infrastructure fixes:
- Twelve test files (memory, context, evaluation, mcp, mcp-http, ppt,
  observability, workflow, tts, media-gen, session-memory-bugs,
  evaluation-scoring) had a shared `PROVIDER_MAX_TOKENS[provider] || 8192`
  default. For local providers with 8K context this set maxTokens to the
  full window → 0 input budget. Fallback lowered to 1024 and the four new
  providers added explicitly to the per-suite map.
- continuous-test-suite-providers.ts: rename `Gemini 3 - DisableTools` test
  to `DisableTools` and make it provider-agnostic (uses TEST_PROVIDER instead
  of hard-coded vertex+gemini-3-flash-preview). The Observability Spans test
  now SKIPs gracefully for Pipeline-A providers (AI SDK + Langfuse OTEL),
  which intentionally do not emit Pipeline-B spans.
- continuous-test-suite-context.ts: Context Compaction Vertex Flash/Pro
  tests now respect TEST_PROVIDER with vertex as fallback.
- continuous-test-suite-evaluation.ts: dimension-specific judge framing in
  scoreAnswerOnDimension so RAGAS Context Precision actually evaluates the
  context (not the answer).
- continuous-test-suite-client.ts: pass timeout: TEST_CONFIG.timeout to
  createServer (was using the undocumented 30s default).
- continuous-test-suite-observability.ts: read TEST_TIMEOUT_MS from env so
  CLI-subprocess tests can be tuned for slow local providers.

Tooling:
- New test/continuous-test-suite-new-providers.ts (10 sections × 4 providers).
- New test/run-provider-matrix.sh runner (bash 3.2 compatible).
- Add test:dynamic, test:proxy, test:bugfixes, test:new-providers scripts.
- Add js-yaml + @types/js-yaml devDeps (proxy Config Loading test fixture).

Documentation:
- docs/provider-integration/ — 14 markdown files covering architecture,
  per-provider integration notes, testing methodology, the failure
  investigation trail, and the PR analysis itself.

Verification:
- `pnpm run check` 0 errors / 0 warnings (3632 files)
- `pnpm run lint` 0 errors / 18 warnings (all pre-existing in unrelated files)
- `pnpm run build` clean
- All 4 providers smoke-tested end-to-end via env, instance, and per-call
  credential paths.
@github-actions

ghost commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
murdore merged commit c829f4d into release Apr 30, 2026
@murdore
murdore deleted the feat/provider-intgeration branch April 30, 2026 14:16
@github-actions

ghost commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 9.60.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — 08dbe193 Deployed Apr 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant