Skip to content

Mirror: Make OpenAI API key optional for local indexing (#5849)#41

Closed
jeremylongshore wants to merge 1 commit intomainfrom
mirror/PR-5849
Closed

Mirror: Make OpenAI API key optional for local indexing (#5849)#41
jeremylongshore wants to merge 1 commit intomainfrom
mirror/PR-5849

Conversation

@jeremylongshore
Copy link
Copy Markdown
Owner

Mirror of Kilo-Org#5849

Field Value
Upstream PR #5849
Author @Neonsy
Category feature
Tier 5
Size 313 lines, 10 files

This PR mirrors the upstream change for multi-AI review analysis.

Bot Review Tracker

  • CodeRabbit
  • Gemini Code Assist
  • Greptile
  • CodeQL
  • Qodo PR-Agent

Links

@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 15, 2026

Warning

Rate limit exceeded

@jeremylongshore has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 58 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mirror/PR-5849

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.

@qodo-code-review
Copy link
Copy Markdown

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Make OpenAI-compatible API key optional for local codebase indexing

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Make OpenAI-compatible API key optional for local codebase indexing
• Update validation logic to only require base URL, not API key
• Add fallback dummy API key when none provided to SDK
• Extend test coverage for empty API key scenarios
Diagram
flowchart LR
  A["OpenAI-compatible Config"] -->|Previously required| B["API Key + Base URL"]
  A -->|Now requires only| C["Base URL"]
  C -->|Optional| D["API Key"]
  D -->|If empty| E["Use Dummy Key EMPTY"]
  E -->|Pass to SDK| F["OpenAI Client"]
  C -->|Direct fetch| G["Omit auth headers if empty"]
Loading

Grey Divider

File Changes

1. src/services/code-index/config-manager.ts ✨ Enhancement +12/-11

Make API key optional in configuration

src/services/code-index/config-manager.ts


2. src/services/code-index/interfaces/config.ts ✨ Enhancement +1/-1

Update type definition for optional API key

src/services/code-index/interfaces/config.ts


3. src/services/code-index/service-factory.ts ✨ Enhancement +4/-2

Remove API key requirement from validation

src/services/code-index/service-factory.ts


View more (7)
4. src/services/code-index/embedders/openai-compatible.ts ✨ Enhancement +21/-13

Handle empty API key with fallback dummy value

src/services/code-index/embedders/openai-compatible.ts


5. src/services/code-index/embedders/__tests__/openai-compatible.spec.ts 🧪 Tests +62/-4

Add tests for empty API key handling

src/services/code-index/embedders/tests/openai-compatible.spec.ts


6. src/services/code-index/__tests__/config-manager.spec.ts 🧪 Tests +98/-3

Add tests for optional API key configuration

src/services/code-index/tests/config-manager.spec.ts


7. src/services/code-index/__tests__/service-factory.spec.ts 🧪 Tests +12/-3

Update test expectations for optional API key

src/services/code-index/tests/service-factory.spec.ts


8. webview-ui/src/components/chat/CodeIndexPopover.tsx ✨ Enhancement +6/-4

Make API key field optional in validation schema

webview-ui/src/components/chat/CodeIndexPopover.tsx


9. webview-ui/src/components/chat/__tests__/CodeIndexPopover.validation.spec.ts 🧪 Tests +51/-0

Add validation tests for optional API key

webview-ui/src/components/chat/tests/CodeIndexPopover.validation.spec.ts


10. .changeset/openai-compatible-codeindex-optional-key.md 📝 Documentation +5/-0

Document optional API key change

.changeset/openai-compatible-codeindex-optional-key.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Dummy key on SDK path 🐞 Bug ✓ Correctness
Description
For non-full endpoint base URLs (e.g. ending in /v1), OpenAICompatibleEmbedder still uses the OpenAI
SDK path, but now instantiates the SDK with a dummy apiKey ("EMPTY") when the real key is empty.
This prevents truly unauthenticated requests for the common “base URL + no key” local setup and can
cause unexpected auth failures.
Code

src/services/code-index/embedders/openai-compatible.ts[R63-78]

+	constructor(baseUrl: string, apiKey: string | undefined, modelId?: string, maxItemTokens?: number) {
		if (!baseUrl) {
			throw new Error(t("embeddings:validation.baseUrlRequired"))
		}
-		if (!apiKey) {
-			throw new Error(t("embeddings:validation.apiKeyRequired"))
-		}

		this.baseUrl = baseUrl
-		this.apiKey = apiKey
+		// kilocode_change
+		this.apiKey = (apiKey ?? "").trim()
+		const sdkApiKey = this.apiKey || OPENAI_COMPATIBLE_DUMMY_API_KEY

		// Wrap OpenAI client creation to handle invalid API key characters
		try {
			this.embeddingsClient = new OpenAI({
				baseURL: baseUrl,
-				apiKey: apiKey,
+				apiKey: sdkApiKey,
			})
Evidence
The constructor replaces an empty key with a dummy key specifically for the OpenAI SDK client.
However, batching and validation still call the OpenAI SDK whenever the provided URL is *not*
detected as a full embeddings endpoint URL; common user input is a “base URL”, and the factory/UI
pass that through unchanged. Therefore, empty-key configs may still send SDK-authenticated requests
rather than truly omitting auth.

src/services/code-index/embedders/openai-compatible.ts[63-88]
src/services/code-index/embedders/openai-compatible.ts[180-194]
src/services/code-index/embedders/openai-compatible.ts[268-291]
src/services/code-index/embedders/openai-compatible.ts[371-390]
src/services/code-index/service-factory.ts[71-80]
webview-ui/src/components/chat/CodeIndexPopover.tsx[1069-1086]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`OpenAICompatibleEmbedder` now accepts an empty API key by creating the OpenAI SDK client with a dummy key (`"EMPTY"`). But for **base URLs** (e.g. `http://localhost:8080/v1`), the embedder still uses the **OpenAI SDK path** (`embeddingsClient.embeddings.create`). That path cannot omit authentication, so the “API key optional” behavior effectively only works for **full endpoint URLs** that trigger the direct-fetch path.

### Issue Context
- Direct-fetch requests correctly omit auth headers when `this.apiKey` is empty.
- The SDK path is still used whenever `isFullUrl` is false, which is common when the UI asks for a “base URL”.

### Fix Focus Areas
- src/services/code-index/embedders/openai-compatible.ts[63-88]
- src/services/code-index/embedders/openai-compatible.ts[180-194]
- src/services/code-index/embedders/openai-compatible.ts[264-291]
- src/services/code-index/embedders/openai-compatible.ts[371-390]

### Suggested implementation direction
- If `this.apiKey` is empty, route both batching and validation through `makeDirectEmbeddingRequest`.
- For non-full URLs, build an embeddings URL safely, e.g. `new URL("embeddings", baseUrl.endsWith("/") ? baseUrl : baseUrl + "/")` (taking care with `/v1` and trailing slashes).
- Add/extend unit tests to cover:
 - baseUrl like `http://localhost:8080/v1` with empty key should NOT send `Authorization`/`api-key` headers
 - validateConfiguration for baseUrl + empty key uses direct fetch and omits auth headers

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +63 to 78
constructor(baseUrl: string, apiKey: string | undefined, modelId?: string, maxItemTokens?: number) {
if (!baseUrl) {
throw new Error(t("embeddings:validation.baseUrlRequired"))
}
if (!apiKey) {
throw new Error(t("embeddings:validation.apiKeyRequired"))
}

this.baseUrl = baseUrl
this.apiKey = apiKey
// kilocode_change
this.apiKey = (apiKey ?? "").trim()
const sdkApiKey = this.apiKey || OPENAI_COMPATIBLE_DUMMY_API_KEY

// Wrap OpenAI client creation to handle invalid API key characters
try {
this.embeddingsClient = new OpenAI({
baseURL: baseUrl,
apiKey: apiKey,
apiKey: sdkApiKey,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Dummy key on sdk path 🐞 Bug ✓ Correctness

For non-full endpoint base URLs (e.g. ending in /v1), OpenAICompatibleEmbedder still uses the OpenAI
SDK path, but now instantiates the SDK with a dummy apiKey ("EMPTY") when the real key is empty.
This prevents truly unauthenticated requests for the common “base URL + no key” local setup and can
cause unexpected auth failures.
Agent Prompt
### Issue description
`OpenAICompatibleEmbedder` now accepts an empty API key by creating the OpenAI SDK client with a dummy key (`"EMPTY"`). But for **base URLs** (e.g. `http://localhost:8080/v1`), the embedder still uses the **OpenAI SDK path** (`embeddingsClient.embeddings.create`). That path cannot omit authentication, so the “API key optional” behavior effectively only works for **full endpoint URLs** that trigger the direct-fetch path.

### Issue Context
- Direct-fetch requests correctly omit auth headers when `this.apiKey` is empty.
- The SDK path is still used whenever `isFullUrl` is false, which is common when the UI asks for a “base URL”.

### Fix Focus Areas
- src/services/code-index/embedders/openai-compatible.ts[63-88]
- src/services/code-index/embedders/openai-compatible.ts[180-194]
- src/services/code-index/embedders/openai-compatible.ts[264-291]
- src/services/code-index/embedders/openai-compatible.ts[371-390]

### Suggested implementation direction
- If `this.apiKey` is empty, route both batching and validation through `makeDirectEmbeddingRequest`.
- For non-full URLs, build an embeddings URL safely, e.g. `new URL("embeddings", baseUrl.endsWith("/") ? baseUrl : baseUrl + "/")` (taking care with `/v1` and trailing slashes).
- Add/extend unit tests to cover:
  - baseUrl like `http://localhost:8080/v1` with empty key should NOT send `Authorization`/`api-key` headers
  - validateConfiguration for baseUrl + empty key uses direct fetch and omits auth headers

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jeremylongshore
Copy link
Copy Markdown
Owner Author

@greptileai review

@jeremylongshore
Copy link
Copy Markdown
Owner Author

@greptileai

@jeremylongshore
Copy link
Copy Markdown
Owner Author

Merged into batch-6-combined-20-mirrors branch for combined testing. Bot reviews collected. Review artifacts preserved in .reviews/.

@jeremylongshore
Copy link
Copy Markdown
Owner Author

Review: kilocode Kilo-Org#5849

Make OpenAI-compatible API key optional for local codebase indexing by @Neonsy

Methodology

Checklist

Check Result Notes
Correctness PASS Fixes reported issue with local/self-hosted embedding providers that need no API key
Conventions PASS Uses kilocode_change markers, exports validation schema for testing
Changeset PASS Patch changeset included
Tests PASS 291 new test lines across 6 test files covering all modified paths
i18n N/A No new user-facing strings
Types PASS Config interface updated to make apiKey optional
Security WARN Uses "EMPTY" as fallback API key -- see finding #2
Scope PASS Focused on code-index OpenAI-compatible path with manager lifecycle fix

Findings

1. Always-initialize pattern in webview handler removes guard checks (severity: yellow)

The webview message handler for "saveCodeIndexSettings" now always calls manager.initialize() regardless of isFeatureEnabled and isFeatureConfigured flags:

// Before: guarded by isFeatureEnabled && isFeatureConfigured
if (currentCodeIndexManager.isFeatureEnabled && currentCodeIndexManager.isFeatureConfigured) {
    if (!currentCodeIndexManager.isInitialized) {
        await currentCodeIndexManager.initialize(provider.contextProxy)
    }
}

// After: always initialize
await currentCodeIndexManager.initialize(provider.contextProxy)

This is intentional -- the author's reasoning is that initialize() must run first so that isFeatureEnabled and isFeatureConfigured have fresh values. The flags depend on loadConfiguration() which happens inside initialize(). Without this change, stale flags could prevent initialization forever. The trade-off is that initialize() is now called even when indexing is disabled, which adds minimal overhead (just loads config).

2. "EMPTY" fallback API key is functional but could leak in logs (severity: yellow)

The embedder uses "EMPTY" as a fallback when no API key is provided:

const OPENAI_COMPATIBLE_DUMMY_API_KEY = "EMPTY"
const sdkApiKey = this.apiKey || OPENAI_COMPATIBLE_DUMMY_API_KEY

This is sent in auth headers for direct fetch mode:

headers["api-key"] = authToken  // "EMPTY"
headers.Authorization = `Bearer ${authToken}`  // "Bearer EMPTY"

This works for local providers like LM Studio that do not validate authentication. However:

  • The string "EMPTY" could appear in server logs, potentially confusing operators
  • A more conventional placeholder like "sk-no-key-required" or just omitting the header might be cleaner
  • The OpenAI SDK constructor requires a non-empty apiKey, so some placeholder is technically necessary

Not blocking -- the behavior is correct for the target use case, and a Discord user confirmed it works with LM Studio.

3. Manager partial-initialization lifecycle fix is sound (severity: gray)

The _recreateServices method now only assigns this._serviceFactory after all services are successfully created:

// Before: assigned immediately, could leave half-initialized state
this._serviceFactory = new CodeIndexServiceFactory(...)
// ... services created from _serviceFactory ...

// After: local variable until success
const serviceFactory = new CodeIndexServiceFactory(...)
// ... services created from serviceFactory ...
this._serviceFactory = serviceFactory  // only after success

And needsServiceRecreation now includes !this.isInitialized:

const needsServiceRecreation = requiresRestart || !this.isInitialized || !this._serviceFactory

This prevents the "CodeIndexManager not initialized" error that occurred when _serviceFactory existed but _orchestrator and _searchService were undefined.

4. Validation schema exported for testing (severity: gray)

createValidationSchema is now exported from CodeIndexPopover.tsx to enable the new validation test file. This is a reasonable test access pattern since the schema is pure logic with no component dependencies.

CI Status

Check Result
compile PASS
test-extension (ubuntu) PASS
test-extension (windows) PASS
test-webview (ubuntu) PASS
test-webview (windows) PASS
test-cli PASS
test-jetbrains PASS
build-cli PASS
check-translations PASS

Code Snippets

Config manager -- API key now optional:

// config-manager.ts
this.openAiCompatibleOptions = openAiCompatibleBaseUrl
    ? {
        baseUrl: openAiCompatibleBaseUrl,
        apiKey: openAiCompatibleApiKey,  // may be empty/undefined
    }
    : undefined

Configuration check -- only base URL and Qdrant required:

// Before: required baseUrl && apiKey && qdrantUrl
const isConfigured = !!(baseUrl && apiKey && qdrantUrl)

// After: only baseUrl && qdrantUrl
const isConfigured = !!(baseUrl && qdrantUrl)

Verdict

APPROVE -- This PR correctly addresses a real user-reported issue where local embedding providers (like LM Studio) could not be used for codebase indexing because the UI required an API key. The fix is comprehensive: config manager, service factory, embedder, webview validation, and manager lifecycle are all updated consistently. The "EMPTY" fallback key is functional for the target use case and the manager partial-initialization fix prevents a genuine lifecycle bug. All CI checks pass and a Discord user confirmed the fix works end-to-end.

@jeremylongshore
Copy link
Copy Markdown
Owner Author

Review Journal: kilocode Kilo-Org#5849

PR: #5849 |
Title: Make OpenAI-compatible API key optional for local codebase indexing |
Author: @Neonsy |
Category: feature | Tier: 5 | Size: 474 lines, 13 files


Summary

Makes API key optional for OpenAI-compatible embedding providers in codebase indexing, enabling local/self-hosted setups like LM Studio. Also fixes a manager lifecycle bug where partial initialization caused "not initialized" errors. Comprehensive changes across config, service factory, embedder, UI validation, and manager. Approve.

First Impressions

This is a multi-layer fix touching 13 files across the code-index subsystem. The PR description is detailed with clear user scenario, implementation breakdown, and manual test steps. The scope is broader than the title suggests -- it also fixes a lifecycle regression in the manager.

What I Looked At

  • src/services/code-index/config-manager.ts -- optional API key in config loading
  • src/services/code-index/service-factory.ts -- service creation with empty key
  • src/services/code-index/embedders/openai-compatible.ts -- embedder constructor and auth handling
  • src/services/code-index/manager.ts -- lifecycle fix (partial initialization guard)
  • src/services/code-index/interfaces/config.ts -- type change
  • src/core/webview/webviewMessageHandler.ts -- always-initialize pattern
  • webview-ui/src/components/chat/CodeIndexPopover.tsx -- validation schema changes
  • All 6 test files for regressions and new coverage
  • Cross-referenced isInitialized getter on main

Analysis

The fix spans four conceptual layers:

  1. Config layer: config-manager.ts and interfaces/config.ts make apiKey optional in the OpenAI-compatible options. The isFeatureConfigured check now only requires baseUrl + qdrantUrl, not apiKey.

  2. Service layer: service-factory.ts no longer throws when API key is missing for OpenAI-compatible. Passes empty string to embedder instead.

  3. Embedder layer: openai-compatible.ts removes the apiKey required check. Uses "EMPTY" as a fallback for the OpenAI SDK constructor (which requires a non-empty string). For direct fetch mode, sends the fallback in both api-key and Authorization headers so keyless servers that still expect auth-shaped headers work.

  4. Lifecycle layer: manager.ts fixes a separate but related bug where _serviceFactory could exist but _orchestrator and _searchService were undefined (partial initialization). The fix:

    • Uses a local variable for serviceFactory until all services are created successfully
    • Includes !this.isInitialized in the needsServiceRecreation check
    • The webview handler always calls initialize() to ensure config flags are fresh

The interaction between these layers is well thought out. The webview handler changes are the most impactful -- removing the isFeatureEnabled && isFeatureConfigured guard means initialize() runs every time settings are saved. This is correct because those flags depend on loadConfiguration() which happens inside initialize().

Verification

  • All CI checks pass
  • Discord user tested with LM Studio and confirmed indexing works with empty API key
  • Test coverage: 6 test files updated/created covering empty key paths, lifecycle regression, validation schema

Lessons Learned

  • Manager lifecycle bugs often stem from assignment timing -- assigning _serviceFactory before all dependent services are ready creates a window where isInitialized checks can pass with missing services
  • The "always initialize" pattern trades minimal overhead for correctness when flags depend on initialization
  • Local embedding providers are an important use case that should not require API keys

Review methodology: AI PR Review Case Studies | Reviewed with GWI + Claude Code

@jeremylongshore jeremylongshore deleted the mirror/PR-5849 branch February 16, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants