feat: add support for custom provider configuration in the user config (#537)

### What

- Add support for loading and merging custom provider configurations
from a local `providers.json` file.
- Allow users to override or extend default providers with their own
settings.

### Why

This change enables users to flexibly customize and extend provider
endpoints and API keys without modifying the codebase, making the CLI
more adaptable for various LLM backends and enterprise use cases.

### How

- Introduced `loadProvidersFromFile` and `getMergedProviders` in config
logic.
- Added/updated related tests in [tests/config.test.tsx]


### Checklist

- [x] Lint passes for changed files
- [x] Tests pass for all files
- [x] Documentation/comments updated as needed

---------

Co-authored-by: Thibault Sottiaux <tibo@openai.com>
This commit is contained in:
kshern
2025-04-23 13:45:56 +08:00
committed by GitHub
parent b428d66f2b
commit 146a61b073
4 changed files with 109 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ import { AutoApprovalMode } from "../src/utils/auto-approval-mode.js";
import { tmpdir } from "os";
import { join } from "path";
import { test, expect, beforeEach, afterEach, vi } from "vitest";
import { providers as defaultProviders } from "../src/utils/providers";
// Inmemory FS store
let memfs: Record<string, string> = {};
@@ -148,3 +149,88 @@ test("loads and saves approvalMode correctly", () => {
});
expect(reloadedConfig.approvalMode).toBe(AutoApprovalMode.FULL_AUTO);
});
test("loads and saves providers correctly", () => {
// Setup custom providers configuration
const customProviders = {
openai: {
name: "Custom OpenAI",
baseURL: "https://custom-api.openai.com/v1",
envKey: "CUSTOM_OPENAI_API_KEY",
},
anthropic: {
name: "Anthropic",
baseURL: "https://api.anthropic.com",
envKey: "ANTHROPIC_API_KEY",
},
};
// Create config with providers
const testConfig = {
model: "test-model",
provider: "anthropic",
providers: customProviders,
instructions: "test instructions",
notify: false,
};
// Save the config
saveConfig(testConfig, testConfigPath, testInstructionsPath);
// Verify saved config contains providers
expect(memfs[testConfigPath]).toContain(`"providers"`);
expect(memfs[testConfigPath]).toContain(`"Custom OpenAI"`);
expect(memfs[testConfigPath]).toContain(`"Anthropic"`);
expect(memfs[testConfigPath]).toContain(`"provider": "anthropic"`);
// Load config and verify providers were loaded correctly
const loadedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check providers were loaded correctly
expect(loadedConfig.provider).toBe("anthropic");
expect(loadedConfig.providers).toEqual({
...defaultProviders,
...customProviders,
});
// Test merging with built-in providers
// Create a config with only one custom provider
const partialProviders = {
customProvider: {
name: "Custom Provider",
baseURL: "https://custom-api.example.com",
envKey: "CUSTOM_API_KEY",
},
};
const partialConfig = {
model: "test-model",
provider: "customProvider",
providers: partialProviders,
instructions: "test instructions",
notify: false,
};
// Save the partial config
saveConfig(partialConfig, testConfigPath, testInstructionsPath);
// Load config and verify providers were merged with built-in providers
const mergedConfig = loadConfig(testConfigPath, testInstructionsPath, {
disableProjectDoc: true,
});
// Check providers is defined
expect(mergedConfig.providers).toBeDefined();
// Use bracket notation to access properties
if (mergedConfig.providers) {
expect(mergedConfig.providers["customProvider"]).toBeDefined();
expect(mergedConfig.providers["customProvider"]).toEqual(
partialProviders.customProvider,
);
// Built-in providers should still be there (like openai)
expect(mergedConfig.providers["openai"]).toBeDefined();
}
});