Skip to content

Commit 467a189

Browse files
authored
feat: add domainMapping in config, to allow for localModels (#5)
* feature: add domainMapping in config, to allow for localModels * feat: so I can install my fork * feature: add links to original repo * feat: update config fns * fix: tests
1 parent 7cfc507 commit 467a189

10 files changed

Lines changed: 485 additions & 257 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,4 @@ help:
102102
@echo "Usage: make [target]"
103103
@echo ""
104104
@echo "Targets:"
105-
@awk '/^##/{c=substr($$0,3);next}c&&/^[[:alpha:]][[:alnum:]_-]+:/{print substr($$1,1,index($$1,":")-1)":"c}1{c=""}' $(MAKEFILE_LIST) | column -t -s ":"
105+
@awk '/^##/{c=substr($$0,3);next}c&&/^[[:alpha:]][[:alnum:]_-]+:/{print substr($$1,1,index($$1,":")-1)":"c}1{c=""}' $(MAKEFILE_LIST) | column -t -s ":"

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,29 @@ router:
374374
web_search: openrouter,perplexity/llama-3.1-sonar-huge-128k-online
375375
```
376376
377+
### 🗺️ Domain Mappings
378+
379+
Map custom domains (like localhost) to existing providers for local model support:
380+
381+
```yaml
382+
# config.yaml
383+
domain_mappings:
384+
localhost: openai # Use OpenAI transformations for localhost requests
385+
127.0.0.1: gemini # Use Gemini transformations for 127.0.0.1 requests
386+
custom.api: openrouter # Use OpenRouter transformations for custom APIs
387+
388+
providers:
389+
name: local-lmstudio
390+
url: "http://localhost:1234/v1/chat/completions"
391+
api_key: "not-needed"
392+
```
393+
394+
**Benefits:**
395+
- ✅ **Local Model Support** - Route localhost to existing providers
396+
- ✅ **Reuse Transformations** - Leverage proven request/response logic
397+
- ✅ **No Custom Provider Needed** - Use existing provider implementations
398+
- ✅ **Flexible Mapping** - Any domain can map to any provider
399+
377400
### 📜 Legacy JSON Format
378401
379402
<details>

Taskfile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,4 @@ tasks:
229229
desc: Run comprehensive checks
230230
deps: [fmt, lint, test, security]
231231
cmds:
232-
- echo 'All checks passed!'
232+
- echo 'All checks passed!'

cmd/config.go

Lines changed: 176 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -139,124 +139,167 @@ func runConfigInit(cmd *cobra.Command, _ []string) error {
139139
}
140140

141141
func runConfigShow(cmd *cobra.Command, _ []string) error {
142-
if !cfgMgr.Exists() {
143-
color.Yellow("No configuration found. Run 'cco config init' or 'cco config generate' to create one.")
144-
return nil
145-
}
146-
147-
cfg, err := cfgMgr.Load()
148-
if err != nil {
149-
return fmt.Errorf("failed to load configuration: %w", err)
150-
}
151-
152-
color.Blue("Current Configuration:")
153-
fmt.Printf(" %-15s: %s\n", "Host", cfg.Host)
154-
fmt.Printf(" %-15s: %d\n", "Port", cfg.Port)
155-
fmt.Printf(" %-15s: %s\n", "API Key", maskString(cfg.APIKey))
156-
fmt.Printf(" %-15s: %s\n", "Config Path", cfgMgr.GetPath())
157-
158-
// Show config file type
159-
configType := "JSON"
160-
if cfgMgr.HasYAML() {
161-
configType = "YAML"
162-
}
163-
164-
fmt.Printf(" %-15s: %s\n", "Format", configType)
165-
166-
fmt.Println("\nProviders:")
167-
168-
for _, provider := range cfg.Providers {
169-
fmt.Printf(" - Name: %s\n", provider.Name)
170-
fmt.Printf(" URL: %s\n", provider.APIBase)
171-
fmt.Printf(" API Key: %s\n", maskString(provider.APIKey))
172-
173-
if len(provider.DefaultModels) > 0 {
174-
fmt.Printf(" Default Models: %v\n", provider.DefaultModels)
175-
}
176-
177-
if len(provider.ModelWhitelist) > 0 {
178-
fmt.Printf(" Model Whitelist: %v\n", provider.ModelWhitelist)
179-
}
180-
181-
if len(provider.Models) > 0 {
182-
fmt.Printf(" Models: %v\n", provider.Models)
183-
}
184-
185-
fmt.Println()
186-
}
187-
188-
fmt.Println("Router Configuration:")
189-
fmt.Printf(" %-15s: %s\n", "Default", cfg.Router.Default)
190-
191-
if cfg.Router.Think != "" {
192-
fmt.Printf(" %-15s: %s\n", "Think", cfg.Router.Think)
193-
}
194-
195-
if cfg.Router.Background != "" {
196-
fmt.Printf(" %-15s: %s\n", "Background", cfg.Router.Background)
197-
}
198-
199-
if cfg.Router.LongContext != "" {
200-
fmt.Printf(" %-15s: %s\n", "Long Context", cfg.Router.LongContext)
201-
}
202-
203-
if cfg.Router.WebSearch != "" {
204-
fmt.Printf(" %-15s: %s\n", "Web Search", cfg.Router.WebSearch)
205-
}
206-
207-
return nil
142+
if !cfgMgr.Exists() {
143+
color.Yellow("No configuration found. Run 'cco config init' or 'cco config generate' to create one.")
144+
return nil
145+
}
146+
147+
cfg, err := cfgMgr.Load()
148+
if err != nil {
149+
return fmt.Errorf("failed to load configuration: %w", err)
150+
}
151+
152+
color.Blue("Current Configuration:")
153+
fmt.Printf(" %-15s: %s\n", "Host", cfg.Host)
154+
fmt.Printf(" %-15s: %d\n", "Port", cfg.Port)
155+
fmt.Printf(" %-15s: %s\n", "API Key", maskString(cfg.APIKey))
156+
fmt.Printf(" %-15s: %s\n", "Config Path", cfgMgr.GetPath())
157+
158+
// Show config file type
159+
configType := "JSON"
160+
if cfgMgr.HasYAML() {
161+
configType = "YAML"
162+
}
163+
fmt.Printf(" %-15s: %s\n", "Format", configType)
164+
165+
// Show domain mappings if present
166+
if len(cfg.DomainMappings) > 0 {
167+
fmt.Println("\nDomain Mappings:")
168+
for domain, provider := range cfg.DomainMappings {
169+
fmt.Printf(" %-15s → %s\n", domain, provider)
170+
}
171+
}
172+
173+
fmt.Println("\nProviders:")
174+
for _, provider := range cfg.Providers {
175+
fmt.Printf(" - Name: %s\n", provider.Name)
176+
fmt.Printf(" URL: %s\n", provider.APIBase)
177+
fmt.Printf(" API Key: %s\n", maskString(provider.APIKey))
178+
179+
if len(provider.DefaultModels) > 0 {
180+
fmt.Printf(" Default Models: %v\n", provider.DefaultModels)
181+
}
182+
183+
if len(provider.ModelWhitelist) > 0 {
184+
fmt.Printf(" Model Whitelist: %v\n", provider.ModelWhitelist)
185+
}
186+
187+
if len(provider.Models) > 0 {
188+
fmt.Printf(" Models: %v\n", provider.Models)
189+
}
190+
191+
fmt.Println()
192+
}
193+
194+
fmt.Println("Router Configuration:")
195+
fmt.Printf(" %-15s: %s\n", "Default", cfg.Router.Default)
196+
197+
if cfg.Router.Think != "" {
198+
fmt.Printf(" %-15s: %s\n", "Think", cfg.Router.Think)
199+
}
200+
201+
if cfg.Router.Background != "" {
202+
fmt.Printf(" %-15s: %s\n", "Background", cfg.Router.Background)
203+
}
204+
205+
if cfg.Router.LongContext != "" {
206+
fmt.Printf(" %-15s: %s\n", "Long Context", cfg.Router.LongContext)
207+
}
208+
209+
if cfg.Router.WebSearch != "" {
210+
fmt.Printf(" %-15s: %s\n", "Web Search", cfg.Router.WebSearch)
211+
}
212+
213+
return nil
208214
}
209215

210-
func runConfigValidate(cmd *cobra.Command, _ []string) error {
211-
if !cfgMgr.Exists() {
212-
return errors.New("no configuration found")
213-
}
214-
215-
cfg, err := cfgMgr.Load()
216-
if err != nil {
217-
return fmt.Errorf("failed to load configuration: %w", err)
218-
}
219-
220-
// Validation logic
221-
var validationErrors []string
222-
223-
if len(cfg.Providers) == 0 {
224-
validationErrors = append(validationErrors, "no providers configured")
225-
}
226-
227-
for i, provider := range cfg.Providers {
228-
if provider.Name == "" {
229-
validationErrors = append(validationErrors, fmt.Sprintf("provider %d: name is required", i))
230-
}
231-
232-
if provider.APIBase == "" {
233-
validationErrors = append(validationErrors, fmt.Sprintf("provider %d: API base URL is required", i))
234-
}
235-
236-
if provider.APIKey == "" {
237-
validationErrors = append(validationErrors, fmt.Sprintf("provider %d: API key is required", i))
238-
}
239-
}
240-
241-
if cfg.Router.Default == "" {
242-
validationErrors = append(validationErrors, "default router model is required")
243-
}
244-
245-
if len(validationErrors) > 0 {
246-
color.Red("Configuration validation failed:")
247-
248-
for _, err := range validationErrors {
249-
fmt.Printf(" - %s\n", err)
250-
}
251216

252-
return errors.New("configuration validation failed")
253-
}
254-
255-
color.Green("Configuration is valid!")
256-
257-
return nil
217+
func runConfigValidate(cmd *cobra.Command, _ []string) error {
218+
if !cfgMgr.Exists() {
219+
return errors.New("no configuration found")
220+
}
221+
222+
cfg, err := cfgMgr.Load()
223+
if err != nil {
224+
return fmt.Errorf("failed to load configuration: %w", err)
225+
}
226+
227+
// Validation logic
228+
var validationErrors []string
229+
230+
if len(cfg.Providers) == 0 {
231+
validationErrors = append(validationErrors, "no providers configured")
232+
}
233+
234+
// Validate providers
235+
providerNames := make(map[string]bool)
236+
for i, provider := range cfg.Providers {
237+
if provider.Name == "" {
238+
validationErrors = append(validationErrors, fmt.Sprintf("provider %d: name is required", i))
239+
} else {
240+
providerNames[provider.Name] = true
241+
}
242+
243+
if provider.APIBase == "" {
244+
validationErrors = append(validationErrors, fmt.Sprintf("provider %d: API base URL is required", i))
245+
}
246+
247+
if provider.APIKey == "" {
248+
validationErrors = append(validationErrors, fmt.Sprintf("provider %d: API key is required", i))
249+
}
250+
}
251+
252+
// Validate domain mappings reference valid providers
253+
if len(cfg.DomainMappings) > 0 {
254+
for domain, providerName := range cfg.DomainMappings {
255+
if domain == "" {
256+
validationErrors = append(validationErrors, "domain mapping: empty domain not allowed")
257+
continue
258+
}
259+
260+
// Check if referenced provider exists (either in config or built-in)
261+
builtInProviders := map[string]bool{
262+
"openrouter": true,
263+
"openai": true,
264+
"anthropic": true,
265+
"nvidia": true,
266+
"gemini": true,
267+
}
268+
269+
if !providerNames[providerName] && !builtInProviders[providerName] {
270+
validationErrors = append(validationErrors,
271+
fmt.Sprintf("domain mapping '%s → %s': provider '%s' not found",
272+
domain, providerName, providerName))
273+
}
274+
}
275+
}
276+
277+
if cfg.Router.Default == "" {
278+
validationErrors = append(validationErrors, "default router model is required")
279+
}
280+
281+
if len(validationErrors) > 0 {
282+
color.Red("Configuration validation failed:")
283+
for _, err := range validationErrors {
284+
fmt.Printf(" - %s\n", err)
285+
}
286+
return errors.New("configuration validation failed")
287+
}
288+
289+
color.Green("Configuration is valid!")
290+
291+
// Show additional info about domain mappings if present
292+
if len(cfg.DomainMappings) > 0 {
293+
color.Cyan("\nDomain mappings configured:")
294+
for domain, provider := range cfg.DomainMappings {
295+
fmt.Printf(" %s → %s\n", domain, provider)
296+
}
297+
}
298+
299+
return nil
258300
}
259301

302+
260303
func runConfigGenerate(cmd *cobra.Command, _ []string) error {
261304
force, err := cmd.Flags().GetBool("force")
262305
if err != nil {
@@ -276,26 +319,26 @@ func runConfigGenerate(cmd *cobra.Command, _ []string) error {
276319
return nil
277320
}
278321

279-
// Generate example YAML config
280-
if err := cfgMgr.CreateExampleYAML(); err != nil {
281-
return fmt.Errorf("failed to create example configuration: %w", err)
282-
}
283-
284-
color.Green("Example YAML configuration created: %s", cfgMgr.GetYAMLPath())
285-
color.Cyan("\nNext steps:")
286-
fmt.Println("1. Edit the configuration file to add your API keys")
287-
fmt.Println("2. Customize provider settings and model whitelists as needed")
288-
fmt.Println("3. Run 'cco config validate' to check your configuration")
289-
fmt.Println("4. Start the router with 'cco start'")
290-
291-
color.Yellow("\nNote: The configuration includes all 5 supported providers:")
292-
fmt.Println("- OpenRouter (access to multiple models)")
293-
fmt.Println("- OpenAI (GPT models)")
294-
fmt.Println("- Anthropic (Claude models)")
295-
fmt.Println("- Nvidia (Nemotron models)")
296-
fmt.Println("- Google Gemini (Gemini models)")
297-
298-
return nil
322+
// Generate example YAML config
323+
if err := cfgMgr.CreateExampleYAML(); err != nil {
324+
return fmt.Errorf("failed to create example configuration: %w", err)
325+
}
326+
327+
color.Green("Example YAML configuration created: %s", cfgMgr.GetYAMLPath())
328+
color.Cyan("\nNext steps:")
329+
fmt.Println("1. Edit the configuration file to add your API keys")
330+
fmt.Println("2. Customize provider settings and model whitelists as needed")
331+
fmt.Println("3. Configure domain_mappings to route local servers to providers")
332+
fmt.Println("4. Run 'cco config validate' to check your configuration")
333+
fmt.Println("5. Start the router with 'cco start'")
334+
335+
color.Yellow("\nNote: The configuration includes:")
336+
fmt.Println("- All 5 supported providers (OpenRouter, OpenAI, Anthropic, Nvidia, Gemini)")
337+
fmt.Println("- Domain mappings for local model support (localhost, 127.0.0.1, etc.)")
338+
fmt.Println("- Example local LM Studio provider configuration")
339+
fmt.Println("- Router configuration for different use cases")
340+
341+
return nil
299342
}
300343

301344
func maskString(s string) string {

0 commit comments

Comments
 (0)