Skip to content

feat(llm): native support for OpenAI, Anthropic, and Gemini#45

Open
saksenaaishwarya wants to merge 1 commit into
nirvik34:mainfrom
saksenaaishwarya:feat/multi-provider-llm
Open

feat(llm): native support for OpenAI, Anthropic, and Gemini#45
saksenaaishwarya wants to merge 1 commit into
nirvik34:mainfrom
saksenaaishwarya:feat/multi-provider-llm

Conversation

@saksenaaishwarya
Copy link
Copy Markdown

@saksenaaishwarya saksenaaishwarya commented May 20, 2026

Summary

Closes #34. Adds a modular LLMProvider interface under src/llm/ so Gitbun can talk to OpenAI, Anthropic, and Gemini in addition to Ollama. Backend is picked via the existing config layer (.smartcommitrc / package.json#smartcommit); API keys come from the environment.

Root cause / motivation

src/llm/ was hardcoded to a local Ollama HTTP API. Contributors and CI users without a GPU asked for hosted-model support but the call sites in src/index.ts imported Ollama-specific functions directly, so adding a backend would have required if/else trees in every call path.

Solution

  • New LLMProvider contract in src/llm/types.ts with isAvailable(), resolveModel(), enhanceCommit().
  • Provider implementations under src/llm/providers/ (ollama.ts, openai.ts, anthropic.ts, gemini.ts), each using the existing node-fetch dependency against the vendor's REST API — no SDK adds.
  • createProvider(config, env) factory in src/llm/index.ts selects the backend; default stays ollama for backwards compatibility.
  • src/index.ts swapped to the factory; spinner / fallback messages are now provider-aware ("openai is not available. Using rule-based commit.").
  • src/llm/checkOllama.ts and src/llm/ollamaEnhancer.ts reduced to thin re-exports so the gitbun-vscode extension and any external imports keep compiling.

Key changes

  • backend config field: ollama (default) | openai | anthropic | gemini.
  • Env-var resolution order per cloud backend: LLM_API_KEY{PROVIDER}_API_KEY.
  • Defaults: gpt-4o-mini, claude-haiku-4-5-20251001, gemini-1.5-flash. Overridable with the existing --model <name> flag or model config key.
  • README updated with a "Remote AI Providers" section + env-var table.

Testing

  • 26 new vitest cases covering each provider (happy path, HTTP error, missing content, network failure, key precedence) plus the factory (backend selection, missing-key behavior).
  • All 52 tests pass; existing analyzer / error tests untouched.
  • npm run lint clean; tsc --noEmit clean.
 Test Files  9 passed (9)
      Tests  52 passed (52)

Backwards compatibility / risks

  • Default backend stays ollama; existing .smartcommitrc files behave identically.
  • OLLAMA_HOST env var still honored.
  • Public exports (enhanceCommit, isOllamaRunning, getBestModel, getAvailableModels) kept at their original paths — the VSCode extension is unaffected.
  • All cloud providers fail soft: missing key / HTTP error / timeout → log a hint and return the original rule-based message. No new fatal paths.
  • No new top-level dependencies.

Checklist

  • Lint passes
  • Type-check passes
  • All tests pass
  • Backwards compatible
  • README updated
  • No new dependencies
  • No telemetry / no analytics

Summary by CodeRabbit

  • New Features

    • Added support for multiple AI providers: OpenAI, Anthropic, and Google Gemini alongside Ollama
    • Configurable AI backend selection via environment variables with LLM_API_KEY preferred
    • Automatic fallback to rule-based commit enhancement when the selected provider is unavailable
  • Documentation

    • Added Remote AI Providers section with configuration examples and supported backends

Review Change Stack

Introduces an LLMProvider contract and factory under src/llm so the
CLI can target Ollama, OpenAI, Anthropic, or Gemini based on a new
`backend` config field. API keys resolve from LLM_API_KEY first, then
the provider-specific variable (OPENAI_API_KEY, ANTHROPIC_API_KEY,
GEMINI_API_KEY). When the selected backend is unavailable, behavior
matches today's Ollama-down path and falls back to the rule-based
engine.

Existing public exports in src/llm/checkOllama and src/llm/ollamaEnhancer
are preserved as thin re-exports so the gitbun-vscode extension and any
external consumers keep compiling.

Closes nirvik34#34
@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

@saksenaaishwarya is attempting to deploy a commit to the nirvik34's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid enum value. Expected 'chill' | 'assertive', received 'professional' at "reviews.profile"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

The PR implements a pluggable LLM provider system supporting OpenAI, Anthropic, Gemini, and Ollama. It introduces a shared LLMProvider interface, centralizes configuration and API key handling, extracts Ollama logic into a provider module, and updates the main entrypoint to select providers dynamically via a factory function. Each cloud provider is tested for availability, API integration, and error fallback behavior.

Changes

Multi-Provider LLM Support

Layer / File(s) Summary
Type system and utilities
src/llm/types.ts
Introduces ProviderName, LLMProvider interface, ProviderConfig, and ProviderEnv types. Implements resolveApiKey() to handle generic LLM_API_KEY precedence over provider-specific keys. Exports COMMIT_SYSTEM_PROMPT, buildUserPrompt(), and cleanCommitOutput() helpers used by all providers.
Provider factory pattern
src/llm/index.ts, src/llm/factory.test.ts
createProvider() factory dispatches to backend-specific provider creators (openai, anthropic, gemini, ollama) with exhaustiveness checking. Tests validate backend selection, API key gating for cloud providers, and isAvailable()/enhanceCommit() behavior.
Cloud provider implementations
src/llm/providers/openai.ts, src/llm/providers/anthropic.ts, src/llm/providers/gemini.ts, src/llm/providers/*.test.ts
Each provider implements isAvailable(), resolveModel(), and enhanceCommit() using a 20s timeout, system/user prompts, and graceful fallback to original message on any error. Tests cover API key precedence, request formation, response parsing, and error paths including missing content and HTTP failures.
Ollama provider refactor
src/llm/providers/ollama.ts, src/llm/checkOllama.ts, src/llm/ollamaEnhancer.ts
Ollama logic extracted into a provider module with functions for availability, model discovery (preferred-name matching with fallback), and commit enhancement. checkOllama.ts now re-exports from the provider module; ollamaEnhancer.ts delegates to enhanceCommitOllama().
Main entrypoint integration
src/index.ts, README.md
AI enhancement block replaced with provider-based flow: creates provider via factory, checks availability, resolves model from config/options, calls provider.enhanceCommit(), and falls back to rule-based engine on unavailability or exceptions. Documentation added describing all supported backends, environment variable setup, and fallback behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

level: advanced, quality:clean, type:testing

Suggested reviewers

  • 2PieRadian

Poem

🐰 Four backends now dance in harmony,
From OpenAI to Gemini with glee,
A factory dispatches with type-safe care,
Fallbacks ensure robustness everywhere,
Ollama still leads, but clouds have their share! ☁️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 clearly and concisely summarizes the main change: adding native support for three cloud LLM providers.
Linked Issues check ✅ Passed All coding objectives from issue #34 are met: modular provider interface, backend selection via config, API key resolution, soft-failure behavior, and backward compatibility with Ollama.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #34's objectives; no unrelated modifications outside the provider abstraction, config, or documentation scope.

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

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

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/llm/providers/gemini.ts`:
- Around line 52-55: The request currently embeds apiKey into the request URL
(constructed via GEMINI_BASE_URL, model, and apiKey) and later logs the raw
error object on failures, risking credential leakage; change the request to
remove apiKey from the URL (keep GEMINI_BASE_URL and model encoding) and send
the key in a header (e.g., Authorization: Bearer <apiKey> or an appropriate
X-API-Key header) instead, and replace the raw error dump with a sanitized log
that includes only non-sensitive details (error.message, status code or a
generic failure message) and ensure any logged strings do not contain apiKey or
its encoded form.

In `@src/llm/providers/ollama.ts`:
- Around line 133-135: In resolveModel, the function currently returns the
user-provided requested value as-is which allows whitespace-only strings to
bypass model discovery; change the logic in resolveModel to trim requested
(e.g., const r = requested?.trim()) and only return it when r is a non-empty
string, otherwise call getBestModel(env) and return that (or null) so
whitespace-only input doesn't skip auto-selection.

In `@src/llm/types.ts`:
- Around line 61-65: cleanCommitOutput currently only strips a single wrapper
char and splits on "\n", which leaves triple-backtick fences and CRLF sequences
intact and also the function does not return the final value; update
cleanCommitOutput to first normalize line endings (convert CRLF to LF), then
trim whitespace, strip any surrounding fence or quote sequences (handle
one-or-more leading/trailing backticks or quotes and optional fenced-language
markers like ```js), remove a leading case-insensitive "commit:" token, then
take the first line (split on LF) and return that string; reference the
cleanCommitOutput function to locate and implement these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca047072-a538-4712-91f8-33338404b743

📥 Commits

Reviewing files that changed from the base of the PR and between 6d4b2de and 341cf7e.

📒 Files selected for processing (14)
  • README.md
  • src/index.ts
  • src/llm/checkOllama.ts
  • src/llm/factory.test.ts
  • src/llm/index.ts
  • src/llm/ollamaEnhancer.ts
  • src/llm/providers/anthropic.test.ts
  • src/llm/providers/anthropic.ts
  • src/llm/providers/gemini.test.ts
  • src/llm/providers/gemini.ts
  • src/llm/providers/ollama.ts
  • src/llm/providers/openai.test.ts
  • src/llm/providers/openai.ts
  • src/llm/types.ts

Comment on lines +52 to +55
const url = `${GEMINI_BASE_URL}/${encodeURIComponent(
model
)}:generateContent?key=${encodeURIComponent(apiKey)}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid potential API-key leakage in error logs.

Line 52-Line 55 places the key in the URL, and Line 87 logs the raw error object. On request failures, this can leak credentials into logs. Log a sanitized message instead of dumping the full error object.

🔒 Proposed fix
-      } catch (error) {
-        console.log("\nAI Enhancement Failed:", error);
+      } catch (error) {
+        const reason = error instanceof Error ? error.name : "unknown";
+        console.log(`\nGemini request failed (${reason}).`);
         return originalMessage;
       } finally {

Also applies to: 86-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llm/providers/gemini.ts` around lines 52 - 55, The request currently
embeds apiKey into the request URL (constructed via GEMINI_BASE_URL, model, and
apiKey) and later logs the raw error object on failures, risking credential
leakage; change the request to remove apiKey from the URL (keep GEMINI_BASE_URL
and model encoding) and send the key in a header (e.g., Authorization: Bearer
<apiKey> or an appropriate X-API-Key header) instead, and replace the raw error
dump with a sanitized log that includes only non-sensitive details
(error.message, status code or a generic failure message) and ensure any logged
strings do not contain apiKey or its encoded form.

Comment on lines +133 to +135
async resolveModel(requested?: string): Promise<string | null> {
if (requested) return requested;
const best = await getBestModel(env);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Trim user-provided model before bypassing auto-selection.

Line 134 returns requested as-is. A whitespace-only value will skip discovery and send an invalid model downstream.

✅ Proposed fix
     async resolveModel(requested?: string): Promise<string | null> {
-      if (requested) return requested;
+      const trimmed = requested?.trim();
+      if (trimmed) return trimmed;
       const best = await getBestModel(env);
       return best ?? FALLBACK_MODEL;
     },
📝 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
async resolveModel(requested?: string): Promise<string | null> {
if (requested) return requested;
const best = await getBestModel(env);
async resolveModel(requested?: string): Promise<string | null> {
const trimmed = requested?.trim();
if (trimmed) return trimmed;
const best = await getBestModel(env);
return best ?? FALLBACK_MODEL;
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llm/providers/ollama.ts` around lines 133 - 135, In resolveModel, the
function currently returns the user-provided requested value as-is which allows
whitespace-only strings to bypass model discovery; change the logic in
resolveModel to trim requested (e.g., const r = requested?.trim()) and only
return it when r is a non-empty string, otherwise call getBestModel(env) and
return that (or null) so whitespace-only input doesn't skip auto-selection.

Comment thread src/llm/types.ts
Comment on lines +61 to +65
export function cleanCommitOutput(raw: string): string {
let result = raw.trim();
result = result.replace(/^[`"']|[`"']$/g, "");
result = result.replace(/^commit:\s*/i, "");
result = result.split("\n")[0];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden output cleaning for fenced/CRLF responses.

Current stripping removes only a single wrapper character and can leave malformed output for responses like triple backticks or CRLF lines.

Suggested patch
 export function cleanCommitOutput(raw: string): string {
   let result = raw.trim();
-  result = result.replace(/^[`"']|[`"']$/g, "");
+  result = result.replace(/^[`"']+|[`"']+$/g, "");
   result = result.replace(/^commit:\s*/i, "");
-  result = result.split("\n")[0];
-  return result;
+  result = result.split(/\r?\n/)[0]?.trim() ?? "";
+  return result;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/llm/types.ts` around lines 61 - 65, cleanCommitOutput currently only
strips a single wrapper char and splits on "\n", which leaves triple-backtick
fences and CRLF sequences intact and also the function does not return the final
value; update cleanCommitOutput to first normalize line endings (convert CRLF to
LF), then trim whitespace, strip any surrounding fence or quote sequences
(handle one-or-more leading/trailing backticks or quotes and optional
fenced-language markers like ```js), remove a leading case-insensitive "commit:"
token, then take the first line (split on LF) and return that string; reference
the cleanCommitOutput function to locate and implement these changes.

Copy link
Copy Markdown
Owner

@nirvik34 nirvik34 left a comment

Choose a reason for hiding this comment

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

Resolve the merge conflict
seciruty issue of gemini api... Move the API key to a header (e.g., Authorization: Bearer or X-API-Key) and remove it from the URL
In ollama.ts trim the requested value and only return it if non-empty; otherwise call getBestModel().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Native CLI Support for OpenAI, Anthropic, and Gemini APIs

2 participants