gencode-ai 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gencode/settings.local.json +7 -0
- package/README.md +20 -102
- package/dist/agent/agent.d.ts +43 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +90 -17
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +9 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/components/AllModelsSelector.d.ts +11 -0
- package/dist/cli/components/AllModelsSelector.d.ts.map +1 -0
- package/dist/cli/components/AllModelsSelector.js +153 -0
- package/dist/cli/components/AllModelsSelector.js.map +1 -0
- package/dist/cli/components/App.d.ts +8 -1
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +276 -40
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +3 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Header.d.ts +1 -1
- package/dist/cli/components/Header.d.ts.map +1 -1
- package/dist/cli/components/Header.js +4 -6
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/Logo.d.ts +1 -0
- package/dist/cli/components/Logo.d.ts.map +1 -1
- package/dist/cli/components/Logo.js +16 -3
- package/dist/cli/components/Logo.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +17 -3
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +70 -18
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModelSelector.d.ts +7 -7
- package/dist/cli/components/ModelSelector.d.ts.map +1 -1
- package/dist/cli/components/ModelSelector.js +116 -33
- package/dist/cli/components/ModelSelector.js.map +1 -1
- package/dist/cli/components/PermissionPrompt.d.ts +60 -0
- package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
- package/dist/cli/components/PermissionPrompt.js +192 -0
- package/dist/cli/components/PermissionPrompt.js.map +1 -0
- package/dist/cli/components/ProviderManager.d.ts +8 -0
- package/dist/cli/components/ProviderManager.d.ts.map +1 -0
- package/dist/cli/components/ProviderManager.js +280 -0
- package/dist/cli/components/ProviderManager.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +7 -2
- package/dist/cli/components/Spinner.d.ts.map +1 -1
- package/dist/cli/components/Spinner.js +116 -25
- package/dist/cli/components/Spinner.js.map +1 -1
- package/dist/cli/components/TodoList.d.ts +7 -0
- package/dist/cli/components/TodoList.d.ts.map +1 -0
- package/dist/cli/components/TodoList.js +34 -0
- package/dist/cli/components/TodoList.js.map +1 -0
- package/dist/cli/components/index.d.ts +1 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +1 -0
- package/dist/cli/components/index.js.map +1 -1
- package/dist/cli/components/markdown.d.ts +9 -0
- package/dist/cli/components/markdown.d.ts.map +1 -0
- package/dist/cli/components/markdown.js +129 -0
- package/dist/cli/components/markdown.js.map +1 -0
- package/dist/cli/components/theme.d.ts +5 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +7 -0
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/cli/index.js +66 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +14 -4
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -3
- package/dist/config/index.js.map +1 -1
- package/dist/config/levels.d.ts +49 -0
- package/dist/config/levels.d.ts.map +1 -0
- package/dist/config/levels.js +222 -0
- package/dist/config/levels.js.map +1 -0
- package/dist/config/loader.d.ts +46 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +153 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/manager.d.ts +115 -15
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +260 -34
- package/dist/config/manager.js.map +1 -1
- package/dist/config/manager.test.d.ts +5 -0
- package/dist/config/manager.test.d.ts.map +1 -0
- package/dist/config/manager.test.js +192 -0
- package/dist/config/manager.test.js.map +1 -0
- package/dist/config/merger.d.ts +56 -0
- package/dist/config/merger.d.ts.map +1 -0
- package/dist/config/merger.js +177 -0
- package/dist/config/merger.js.map +1 -0
- package/dist/config/providers-config.d.ts +28 -0
- package/dist/config/providers-config.d.ts.map +1 -0
- package/dist/config/providers-config.js +79 -0
- package/dist/config/providers-config.js.map +1 -0
- package/dist/config/test-utils.d.ts +24 -0
- package/dist/config/test-utils.d.ts.map +1 -0
- package/dist/config/test-utils.js +55 -0
- package/dist/config/test-utils.js.map +1 -0
- package/dist/config/types.d.ts +108 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +53 -2
- package/dist/config/types.js.map +1 -1
- package/dist/memory/import-resolver.d.ts +46 -0
- package/dist/memory/import-resolver.d.ts.map +1 -0
- package/dist/memory/import-resolver.js +117 -0
- package/dist/memory/import-resolver.js.map +1 -0
- package/dist/memory/index.d.ts +7 -6
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +7 -5
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/init-prompt.d.ts +22 -0
- package/dist/memory/init-prompt.d.ts.map +1 -0
- package/dist/memory/init-prompt.js +103 -0
- package/dist/memory/init-prompt.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +119 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +587 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/memory/rules-parser.d.ts +38 -0
- package/dist/memory/rules-parser.d.ts.map +1 -0
- package/dist/memory/rules-parser.js +69 -0
- package/dist/memory/rules-parser.js.map +1 -0
- package/dist/memory/test-utils.d.ts +20 -0
- package/dist/memory/test-utils.d.ts.map +1 -0
- package/dist/memory/test-utils.js +44 -0
- package/dist/memory/test-utils.js.map +1 -0
- package/dist/memory/types.d.ts +70 -63
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +42 -2
- package/dist/memory/types.js.map +1 -1
- package/dist/permissions/audit.d.ts +82 -0
- package/dist/permissions/audit.d.ts.map +1 -0
- package/dist/permissions/audit.js +229 -0
- package/dist/permissions/audit.js.map +1 -0
- package/dist/permissions/index.d.ts +11 -1
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +15 -0
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/manager.d.ts +149 -13
- package/dist/permissions/manager.d.ts.map +1 -1
- package/dist/permissions/manager.js +480 -35
- package/dist/permissions/manager.js.map +1 -1
- package/dist/permissions/manager.test.d.ts +5 -0
- package/dist/permissions/manager.test.d.ts.map +1 -0
- package/dist/permissions/manager.test.js +213 -0
- package/dist/permissions/manager.test.js.map +1 -0
- package/dist/permissions/persistence.d.ts +74 -0
- package/dist/permissions/persistence.d.ts.map +1 -0
- package/dist/permissions/persistence.js +248 -0
- package/dist/permissions/persistence.js.map +1 -0
- package/dist/permissions/persistence.test.d.ts +5 -0
- package/dist/permissions/persistence.test.d.ts.map +1 -0
- package/dist/permissions/persistence.test.js +171 -0
- package/dist/permissions/persistence.test.js.map +1 -0
- package/dist/permissions/prompt-matcher.d.ts +64 -0
- package/dist/permissions/prompt-matcher.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.js +415 -0
- package/dist/permissions/prompt-matcher.js.map +1 -0
- package/dist/permissions/prompt-matcher.test.d.ts +5 -0
- package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.test.js +107 -0
- package/dist/permissions/prompt-matcher.test.js.map +1 -0
- package/dist/permissions/types.d.ts +157 -0
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +43 -8
- package/dist/permissions/types.js.map +1 -1
- package/dist/prompts/index.d.ts +92 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +241 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +14 -3
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +5 -3
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +13 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/registry.d.ts +66 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +158 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/search/brave.d.ts +14 -0
- package/dist/providers/search/brave.d.ts.map +1 -0
- package/dist/providers/search/brave.js +87 -0
- package/dist/providers/search/brave.js.map +1 -0
- package/dist/providers/search/exa.d.ts +12 -0
- package/dist/providers/search/exa.d.ts.map +1 -0
- package/dist/providers/search/exa.js +158 -0
- package/dist/providers/search/exa.js.map +1 -0
- package/dist/providers/search/index.d.ts +31 -0
- package/dist/providers/search/index.d.ts.map +1 -0
- package/dist/providers/search/index.js +75 -0
- package/dist/providers/search/index.js.map +1 -0
- package/dist/providers/search/serper.d.ts +14 -0
- package/dist/providers/search/serper.d.ts.map +1 -0
- package/dist/providers/search/serper.js +87 -0
- package/dist/providers/search/serper.js.map +1 -0
- package/dist/providers/search/types.d.ts +21 -0
- package/dist/providers/search/types.d.ts.map +1 -0
- package/dist/providers/search/types.js +5 -0
- package/dist/providers/search/types.js.map +1 -0
- package/dist/providers/store.d.ts +104 -0
- package/dist/providers/store.d.ts.map +1 -0
- package/dist/providers/store.js +171 -0
- package/dist/providers/store.js.map +1 -0
- package/dist/providers/types.d.ts +7 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts +33 -0
- package/dist/providers/vertex-ai.d.ts.map +1 -0
- package/dist/providers/vertex-ai.js +407 -0
- package/dist/providers/vertex-ai.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -1
- package/dist/tools/builtin/bash.js +2 -1
- package/dist/tools/builtin/bash.js.map +1 -1
- package/dist/tools/builtin/edit.d.ts.map +1 -1
- package/dist/tools/builtin/edit.js +2 -1
- package/dist/tools/builtin/edit.js.map +1 -1
- package/dist/tools/builtin/glob.d.ts.map +1 -1
- package/dist/tools/builtin/glob.js +2 -1
- package/dist/tools/builtin/glob.js.map +1 -1
- package/dist/tools/builtin/grep.d.ts.map +1 -1
- package/dist/tools/builtin/grep.js +2 -1
- package/dist/tools/builtin/grep.js.map +1 -1
- package/dist/tools/builtin/read.d.ts.map +1 -1
- package/dist/tools/builtin/read.js +2 -1
- package/dist/tools/builtin/read.js.map +1 -1
- package/dist/tools/builtin/todowrite.d.ts +15 -0
- package/dist/tools/builtin/todowrite.d.ts.map +1 -0
- package/dist/tools/builtin/todowrite.js +88 -0
- package/dist/tools/builtin/todowrite.js.map +1 -0
- package/dist/tools/builtin/webfetch.d.ts +20 -0
- package/dist/tools/builtin/webfetch.d.ts.map +1 -0
- package/dist/tools/builtin/webfetch.js +228 -0
- package/dist/tools/builtin/webfetch.js.map +1 -0
- package/dist/tools/builtin/websearch.d.ts +17 -0
- package/dist/tools/builtin/websearch.d.ts.map +1 -0
- package/dist/tools/builtin/websearch.js +87 -0
- package/dist/tools/builtin/websearch.js.map +1 -0
- package/dist/tools/builtin/write.d.ts.map +1 -1
- package/dist/tools/builtin/write.js +2 -1
- package/dist/tools/builtin/write.js.map +1 -1
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +28 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +41 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +16 -0
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/utils/ssrf.d.ts +18 -0
- package/dist/tools/utils/ssrf.d.ts.map +1 -0
- package/dist/tools/utils/ssrf.js +70 -0
- package/dist/tools/utils/ssrf.js.map +1 -0
- package/docs/README.md +5 -4
- package/docs/config-system-comparison.md +707 -0
- package/docs/memory-system.md +238 -0
- package/docs/permissions.md +368 -0
- package/docs/proposals/0001-web-fetch-tool.md +32 -2
- package/docs/proposals/0002-web-search-tool.md +59 -2
- package/docs/proposals/0005-todo-system.md +350 -85
- package/docs/proposals/0006-memory-system.md +11 -10
- package/docs/proposals/0012-ask-user-question.md +941 -206
- package/docs/proposals/0023-permission-enhancements.md +61 -2
- package/docs/proposals/0041-configuration-system.md +587 -0
- package/docs/proposals/0042-prompt-optimization.md +866 -0
- package/docs/proposals/README.md +8 -6
- package/docs/providers.md +220 -0
- package/jest.config.js +26 -0
- package/package.json +14 -3
- package/src/agent/agent.ts +120 -18
- package/src/agent/types.ts +9 -1
- package/src/cli/components/App.tsx +369 -47
- package/src/cli/components/CommandSuggestions.tsx +3 -0
- package/src/cli/components/Header.tsx +11 -17
- package/src/cli/components/Logo.tsx +76 -9
- package/src/cli/components/Messages.tsx +146 -38
- package/src/cli/components/ModelSelector.tsx +169 -52
- package/src/cli/components/PermissionPrompt.tsx +388 -0
- package/src/cli/components/ProviderManager.tsx +534 -0
- package/src/cli/components/Spinner.tsx +138 -25
- package/src/cli/components/TodoList.tsx +54 -0
- package/src/cli/components/index.ts +6 -0
- package/src/cli/components/markdown.ts +157 -0
- package/src/cli/components/theme.ts +7 -0
- package/src/cli/index.tsx +76 -13
- package/src/config/index.ts +79 -4
- package/src/config/levels.test.ts +163 -0
- package/src/config/levels.ts +285 -0
- package/src/config/loader.test.ts +120 -0
- package/src/config/loader.ts +178 -0
- package/src/config/manager.test.ts +215 -0
- package/src/config/manager.ts +328 -40
- package/src/config/merger.test.ts +360 -0
- package/src/config/merger.ts +221 -0
- package/src/config/providers-config.ts +85 -0
- package/src/config/test-utils.ts +79 -0
- package/src/config/types.ts +186 -9
- package/src/memory/import-resolver.test.ts +117 -0
- package/src/memory/import-resolver.ts +149 -0
- package/src/memory/index.ts +11 -0
- package/src/memory/init-prompt.ts +113 -0
- package/src/memory/memory-manager.test.ts +198 -0
- package/src/memory/memory-manager.ts +716 -0
- package/src/memory/rules-parser.test.ts +182 -0
- package/src/memory/rules-parser.ts +82 -0
- package/src/memory/test-utils.ts +60 -0
- package/src/memory/types.ts +119 -0
- package/src/permissions/audit.ts +284 -0
- package/src/permissions/index.ts +20 -1
- package/src/permissions/manager.test.ts +260 -0
- package/src/permissions/manager.ts +592 -40
- package/src/permissions/persistence.test.ts +220 -0
- package/src/permissions/persistence.ts +301 -0
- package/src/permissions/prompt-matcher.test.ts +213 -0
- package/src/permissions/prompt-matcher.ts +472 -0
- package/src/permissions/types.ts +236 -8
- package/src/prompts/index.test.ts +279 -0
- package/src/prompts/index.ts +306 -0
- package/src/prompts/system/anthropic.txt +29 -0
- package/src/prompts/system/base.txt +124 -0
- package/src/prompts/system/gemini.txt +35 -0
- package/src/prompts/system/generic.txt +128 -0
- package/src/prompts/system/openai.txt +29 -0
- package/src/prompts/tools/bash.txt +60 -0
- package/src/prompts/tools/edit.txt +29 -0
- package/src/prompts/tools/glob.txt +35 -0
- package/src/prompts/tools/grep.txt +43 -0
- package/src/prompts/tools/read.txt +22 -0
- package/src/prompts/tools/todowrite.txt +71 -0
- package/src/prompts/tools/webfetch.txt +34 -0
- package/src/prompts/tools/websearch.txt +41 -0
- package/src/prompts/tools/write.txt +23 -0
- package/src/providers/gemini.ts +20 -4
- package/src/providers/index.ts +18 -3
- package/src/providers/registry.ts +198 -0
- package/src/providers/search/brave.ts +132 -0
- package/src/providers/search/exa.ts +217 -0
- package/src/providers/search/index.ts +79 -0
- package/src/providers/search/serper.ts +133 -0
- package/src/providers/search/types.ts +24 -0
- package/src/providers/store.ts +216 -0
- package/src/providers/types.ts +9 -1
- package/src/providers/vertex-ai.ts +594 -0
- package/src/tools/builtin/bash.ts +2 -1
- package/src/tools/builtin/edit.ts +2 -1
- package/src/tools/builtin/glob.ts +2 -1
- package/src/tools/builtin/grep.ts +2 -1
- package/src/tools/builtin/read.ts +2 -1
- package/src/tools/builtin/todowrite.ts +102 -0
- package/src/tools/builtin/webfetch.ts +261 -0
- package/src/tools/builtin/websearch.ts +103 -0
- package/src/tools/builtin/write.ts +2 -1
- package/src/tools/index.ts +28 -2
- package/src/tools/types.ts +32 -0
- package/src/tools/utils/ssrf.ts +79 -0
- package/tsconfig.json +1 -1
- package/CLAUDE.md +0 -70
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Merger Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from '@jest/globals';
|
|
6
|
+
import {
|
|
7
|
+
deepMerge,
|
|
8
|
+
mergeSettings,
|
|
9
|
+
extractManagedDeny,
|
|
10
|
+
applyManagedRestrictions,
|
|
11
|
+
mergeAllSources,
|
|
12
|
+
mergeWithCliArgs,
|
|
13
|
+
createMergeSummary,
|
|
14
|
+
} from './merger.js';
|
|
15
|
+
import type { ConfigSource, Settings } from './types.js';
|
|
16
|
+
|
|
17
|
+
describe('deepMerge', () => {
|
|
18
|
+
it('should merge simple objects', () => {
|
|
19
|
+
const base = { a: 1, b: 2 };
|
|
20
|
+
const override = { b: 3, c: 4 };
|
|
21
|
+
const result = deepMerge(base, override);
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should deep merge nested objects', () => {
|
|
27
|
+
const base = {
|
|
28
|
+
provider: 'openai',
|
|
29
|
+
permissions: {
|
|
30
|
+
allow: ['Bash(git:*)'],
|
|
31
|
+
deny: ['WebFetch'],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const override = {
|
|
35
|
+
model: 'claude-sonnet',
|
|
36
|
+
permissions: {
|
|
37
|
+
allow: ['Bash(npm:*)'],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const result = deepMerge(base, override);
|
|
41
|
+
|
|
42
|
+
expect(result.provider).toBe('openai');
|
|
43
|
+
expect(result.model).toBe('claude-sonnet');
|
|
44
|
+
expect(result.permissions.allow).toContain('Bash(git:*)');
|
|
45
|
+
expect(result.permissions.allow).toContain('Bash(npm:*)');
|
|
46
|
+
expect(result.permissions.deny).toContain('WebFetch');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should concatenate and deduplicate arrays', () => {
|
|
50
|
+
const base = { items: ['a', 'b', 'c'] };
|
|
51
|
+
const override = { items: ['b', 'c', 'd'] };
|
|
52
|
+
const result = deepMerge(base, override);
|
|
53
|
+
|
|
54
|
+
expect(result.items).toEqual(['a', 'b', 'c', 'd']);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should override scalar values', () => {
|
|
58
|
+
const base = { model: 'gpt-4', theme: 'dark' };
|
|
59
|
+
const override = { model: 'claude-sonnet' };
|
|
60
|
+
const result = deepMerge(base, override);
|
|
61
|
+
|
|
62
|
+
expect(result.model).toBe('claude-sonnet');
|
|
63
|
+
expect(result.theme).toBe('dark');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle undefined values', () => {
|
|
67
|
+
const base = { a: 1, b: 2 };
|
|
68
|
+
const override = { a: undefined, c: 3 };
|
|
69
|
+
const result = deepMerge(base, override as any);
|
|
70
|
+
|
|
71
|
+
expect(result.a).toBe(1); // undefined should not override
|
|
72
|
+
expect(result.c).toBe(3);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle empty objects', () => {
|
|
76
|
+
const base = { a: 1 };
|
|
77
|
+
const override = {};
|
|
78
|
+
const result = deepMerge(base, override);
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual({ a: 1 });
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('mergeSettings', () => {
|
|
85
|
+
it('should merge multiple sources in order', () => {
|
|
86
|
+
const sources: ConfigSource[] = [
|
|
87
|
+
{
|
|
88
|
+
level: 'user',
|
|
89
|
+
path: '~/.claude/settings.json',
|
|
90
|
+
namespace: 'claude',
|
|
91
|
+
settings: { provider: 'openai', model: 'gpt-4' },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
level: 'user',
|
|
95
|
+
path: '~/.gencode/settings.json',
|
|
96
|
+
namespace: 'gencode',
|
|
97
|
+
settings: { provider: 'anthropic' }, // Override provider
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
level: 'project',
|
|
101
|
+
path: '.gencode/settings.json',
|
|
102
|
+
namespace: 'gencode',
|
|
103
|
+
settings: { model: 'claude-sonnet' }, // Override model
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const result = mergeSettings(sources);
|
|
108
|
+
|
|
109
|
+
expect(result.provider).toBe('anthropic'); // From gencode user
|
|
110
|
+
expect(result.model).toBe('claude-sonnet'); // From project
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should concatenate permission arrays', () => {
|
|
114
|
+
const sources: ConfigSource[] = [
|
|
115
|
+
{
|
|
116
|
+
level: 'user',
|
|
117
|
+
path: '~/.claude/settings.json',
|
|
118
|
+
namespace: 'claude',
|
|
119
|
+
settings: {
|
|
120
|
+
permissions: { allow: ['Bash(git:*)'] },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
level: 'project',
|
|
125
|
+
path: '.gencode/settings.json',
|
|
126
|
+
namespace: 'gencode',
|
|
127
|
+
settings: {
|
|
128
|
+
permissions: { allow: ['Bash(npm:*)'], deny: ['WebFetch'] },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const result = mergeSettings(sources);
|
|
134
|
+
|
|
135
|
+
expect(result.permissions?.allow).toContain('Bash(git:*)');
|
|
136
|
+
expect(result.permissions?.allow).toContain('Bash(npm:*)');
|
|
137
|
+
expect(result.permissions?.deny).toContain('WebFetch');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return empty object for empty sources', () => {
|
|
141
|
+
const result = mergeSettings([]);
|
|
142
|
+
expect(result).toEqual({});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('extractManagedDeny', () => {
|
|
147
|
+
it('should extract deny rules from managed sources', () => {
|
|
148
|
+
const sources: ConfigSource[] = [
|
|
149
|
+
{
|
|
150
|
+
level: 'user',
|
|
151
|
+
path: '~/.gencode/settings.json',
|
|
152
|
+
namespace: 'gencode',
|
|
153
|
+
settings: {
|
|
154
|
+
permissions: { deny: ['WebFetch'] },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
level: 'managed',
|
|
159
|
+
path: '/etc/gencode/managed-settings.json',
|
|
160
|
+
namespace: 'gencode',
|
|
161
|
+
settings: {
|
|
162
|
+
permissions: { deny: ['Bash(curl:*)'] },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const result = extractManagedDeny(sources);
|
|
168
|
+
|
|
169
|
+
// Should only include managed deny rules
|
|
170
|
+
expect(result).toContain('Bash(curl:*)');
|
|
171
|
+
expect(result).not.toContain('WebFetch');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should deduplicate managed deny rules', () => {
|
|
175
|
+
const sources: ConfigSource[] = [
|
|
176
|
+
{
|
|
177
|
+
level: 'managed',
|
|
178
|
+
path: '/Library/.../ClaudeCode/managed-settings.json',
|
|
179
|
+
namespace: 'claude',
|
|
180
|
+
settings: {
|
|
181
|
+
permissions: { deny: ['Bash(curl:*)'] },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
level: 'managed',
|
|
186
|
+
path: '/Library/.../GenCode/managed-settings.json',
|
|
187
|
+
namespace: 'gencode',
|
|
188
|
+
settings: {
|
|
189
|
+
permissions: { deny: ['Bash(curl:*)', 'WebFetch'] },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const result = extractManagedDeny(sources);
|
|
195
|
+
|
|
196
|
+
expect(result).toEqual(['Bash(curl:*)', 'WebFetch']);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should return empty array if no managed sources', () => {
|
|
200
|
+
const sources: ConfigSource[] = [
|
|
201
|
+
{
|
|
202
|
+
level: 'user',
|
|
203
|
+
path: '~/.gencode/settings.json',
|
|
204
|
+
namespace: 'gencode',
|
|
205
|
+
settings: {
|
|
206
|
+
permissions: { deny: ['WebFetch'] },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const result = extractManagedDeny(sources);
|
|
212
|
+
expect(result).toEqual([]);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('applyManagedRestrictions', () => {
|
|
217
|
+
it('should add managed deny rules to deny list', () => {
|
|
218
|
+
const settings: Settings = {
|
|
219
|
+
permissions: {
|
|
220
|
+
allow: ['Bash(git:*)'],
|
|
221
|
+
deny: ['WebFetch'],
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
const managedDeny = ['Bash(curl:*)'];
|
|
225
|
+
|
|
226
|
+
const result = applyManagedRestrictions(settings, managedDeny);
|
|
227
|
+
|
|
228
|
+
expect(result.permissions?.deny).toContain('WebFetch');
|
|
229
|
+
expect(result.permissions?.deny).toContain('Bash(curl:*)');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should remove managed deny from allow list', () => {
|
|
233
|
+
const settings: Settings = {
|
|
234
|
+
permissions: {
|
|
235
|
+
allow: ['Bash(git:*)', 'Bash(curl:*)'], // curl should be removed
|
|
236
|
+
deny: [],
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
const managedDeny = ['Bash(curl:*)'];
|
|
240
|
+
|
|
241
|
+
const result = applyManagedRestrictions(settings, managedDeny);
|
|
242
|
+
|
|
243
|
+
expect(result.permissions?.allow).toContain('Bash(git:*)');
|
|
244
|
+
expect(result.permissions?.allow).not.toContain('Bash(curl:*)');
|
|
245
|
+
expect(result.permissions?.deny).toContain('Bash(curl:*)');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should return unchanged settings if no managed deny', () => {
|
|
249
|
+
const settings: Settings = {
|
|
250
|
+
provider: 'anthropic',
|
|
251
|
+
permissions: { allow: ['Bash(git:*)'] },
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const result = applyManagedRestrictions(settings, []);
|
|
255
|
+
|
|
256
|
+
expect(result).toEqual(settings);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe('mergeAllSources', () => {
|
|
261
|
+
it('should merge all sources and extract managed deny', () => {
|
|
262
|
+
const sources: ConfigSource[] = [
|
|
263
|
+
{
|
|
264
|
+
level: 'user',
|
|
265
|
+
path: '~/.gencode/settings.json',
|
|
266
|
+
namespace: 'gencode',
|
|
267
|
+
settings: { provider: 'anthropic' },
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
level: 'managed',
|
|
271
|
+
path: '/etc/gencode/managed-settings.json',
|
|
272
|
+
namespace: 'gencode',
|
|
273
|
+
settings: {
|
|
274
|
+
permissions: { deny: ['Bash(curl:*)'] },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
const result = mergeAllSources(sources);
|
|
280
|
+
|
|
281
|
+
expect(result.settings.provider).toBe('anthropic');
|
|
282
|
+
expect(result.managedDeny).toContain('Bash(curl:*)');
|
|
283
|
+
expect(result.sources).toHaveLength(2);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('mergeWithCliArgs', () => {
|
|
288
|
+
it('should merge CLI args with highest priority', () => {
|
|
289
|
+
const sources: ConfigSource[] = [
|
|
290
|
+
{
|
|
291
|
+
level: 'user',
|
|
292
|
+
path: '~/.gencode/settings.json',
|
|
293
|
+
namespace: 'gencode',
|
|
294
|
+
settings: { provider: 'openai', model: 'gpt-4' },
|
|
295
|
+
},
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const merged = mergeAllSources(sources);
|
|
299
|
+
const result = mergeWithCliArgs(merged, { model: 'claude-sonnet' });
|
|
300
|
+
|
|
301
|
+
expect(result.settings.provider).toBe('openai');
|
|
302
|
+
expect(result.settings.model).toBe('claude-sonnet');
|
|
303
|
+
expect(result.sources).toHaveLength(2);
|
|
304
|
+
expect(result.sources[1].level).toBe('cli');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should not allow CLI args to override managed deny', () => {
|
|
308
|
+
const sources: ConfigSource[] = [
|
|
309
|
+
{
|
|
310
|
+
level: 'managed',
|
|
311
|
+
path: '/etc/gencode/managed-settings.json',
|
|
312
|
+
namespace: 'gencode',
|
|
313
|
+
settings: {
|
|
314
|
+
permissions: { deny: ['Bash(curl:*)'] },
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
const merged = mergeAllSources(sources);
|
|
320
|
+
// Try to add curl to allow list via CLI
|
|
321
|
+
const result = mergeWithCliArgs(merged, {
|
|
322
|
+
permissions: { allow: ['Bash(curl:*)'] },
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// curl should still be in deny, not in allow
|
|
326
|
+
expect(result.settings.permissions?.deny).toContain('Bash(curl:*)');
|
|
327
|
+
expect(result.settings.permissions?.allow).not.toContain('Bash(curl:*)');
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('createMergeSummary', () => {
|
|
332
|
+
it('should create readable summary', () => {
|
|
333
|
+
const sources: ConfigSource[] = [
|
|
334
|
+
{
|
|
335
|
+
level: 'user',
|
|
336
|
+
path: '~/.gencode/settings.json',
|
|
337
|
+
namespace: 'gencode',
|
|
338
|
+
settings: { provider: 'anthropic' },
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
level: 'managed',
|
|
342
|
+
path: '/etc/gencode/managed-settings.json',
|
|
343
|
+
namespace: 'gencode',
|
|
344
|
+
settings: {
|
|
345
|
+
permissions: { deny: ['Bash(curl:*)'] },
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
const merged = mergeAllSources(sources);
|
|
351
|
+
const summary = createMergeSummary(merged);
|
|
352
|
+
|
|
353
|
+
expect(summary).toContain('Configuration Sources');
|
|
354
|
+
expect(summary).toContain('user:gencode');
|
|
355
|
+
expect(summary).toContain('managed:gencode');
|
|
356
|
+
expect(summary).toContain('[enforced]');
|
|
357
|
+
expect(summary).toContain('Managed Deny Rules');
|
|
358
|
+
expect(summary).toContain('Bash(curl:*)');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Merger - Merge settings from multiple sources
|
|
3
|
+
*
|
|
4
|
+
* Implements the merge strategy:
|
|
5
|
+
* - Scalar values: Higher priority replaces lower
|
|
6
|
+
* - Arrays (permissions.allow, permissions.deny): Concatenate with deduplication
|
|
7
|
+
* - Objects: Deep merge recursively
|
|
8
|
+
* - Managed deny rules: Cannot be overridden by any level
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Settings, ConfigSource, MergedConfig, PermissionRules } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a value is a plain object
|
|
15
|
+
*/
|
|
16
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
17
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Deep merge two objects
|
|
22
|
+
*
|
|
23
|
+
* - Arrays are concatenated and deduplicated
|
|
24
|
+
* - Objects are recursively merged
|
|
25
|
+
* - Scalars from override replace base
|
|
26
|
+
*/
|
|
27
|
+
export function deepMerge<T extends Record<string, unknown>>(
|
|
28
|
+
base: T,
|
|
29
|
+
override: Partial<T>
|
|
30
|
+
): T {
|
|
31
|
+
const result = { ...base };
|
|
32
|
+
|
|
33
|
+
for (const key in override) {
|
|
34
|
+
const baseValue = result[key];
|
|
35
|
+
const overrideValue = override[key];
|
|
36
|
+
|
|
37
|
+
if (overrideValue === undefined) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {
|
|
42
|
+
// Recursively merge objects
|
|
43
|
+
result[key] = deepMerge(
|
|
44
|
+
baseValue as Record<string, unknown>,
|
|
45
|
+
overrideValue as Record<string, unknown>
|
|
46
|
+
) as T[Extract<keyof T, string>];
|
|
47
|
+
} else if (Array.isArray(baseValue) && Array.isArray(overrideValue)) {
|
|
48
|
+
// Concatenate arrays and deduplicate
|
|
49
|
+
result[key] = deduplicateArray([
|
|
50
|
+
...baseValue,
|
|
51
|
+
...overrideValue,
|
|
52
|
+
]) as T[Extract<keyof T, string>];
|
|
53
|
+
} else {
|
|
54
|
+
// Override scalar values
|
|
55
|
+
result[key] = overrideValue as T[Extract<keyof T, string>];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Deduplicate an array while preserving order
|
|
64
|
+
*/
|
|
65
|
+
function deduplicateArray<T>(arr: T[]): T[] {
|
|
66
|
+
const seen = new Set<string>();
|
|
67
|
+
const result: T[] = [];
|
|
68
|
+
|
|
69
|
+
for (const item of arr) {
|
|
70
|
+
const key = typeof item === 'object' ? JSON.stringify(item) : String(item);
|
|
71
|
+
if (!seen.has(key)) {
|
|
72
|
+
seen.add(key);
|
|
73
|
+
result.push(item);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Merge all configuration sources into a single settings object
|
|
82
|
+
*
|
|
83
|
+
* Sources should be in priority order (lowest first).
|
|
84
|
+
* Each source is merged on top of the previous result.
|
|
85
|
+
*/
|
|
86
|
+
export function mergeSettings(sources: ConfigSource[]): Settings {
|
|
87
|
+
let merged: Settings = {};
|
|
88
|
+
|
|
89
|
+
for (const source of sources) {
|
|
90
|
+
merged = deepMerge(merged, source.settings);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return merged;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extract managed deny rules from sources
|
|
98
|
+
*
|
|
99
|
+
* These rules are extracted from managed-level sources and cannot be
|
|
100
|
+
* overridden by any lower-level configuration.
|
|
101
|
+
*/
|
|
102
|
+
export function extractManagedDeny(sources: ConfigSource[]): string[] {
|
|
103
|
+
const managedDeny: string[] = [];
|
|
104
|
+
|
|
105
|
+
for (const source of sources) {
|
|
106
|
+
if (source.level === 'managed') {
|
|
107
|
+
const deny = source.settings.permissions?.deny;
|
|
108
|
+
if (deny) {
|
|
109
|
+
managedDeny.push(...deny);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return deduplicateArray(managedDeny);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Apply managed restrictions to the merged settings
|
|
119
|
+
*
|
|
120
|
+
* - Ensures managed deny rules are always in the deny list
|
|
121
|
+
* - Removes managed deny patterns from allow list
|
|
122
|
+
*/
|
|
123
|
+
export function applyManagedRestrictions(
|
|
124
|
+
settings: Settings,
|
|
125
|
+
managedDeny: string[]
|
|
126
|
+
): Settings {
|
|
127
|
+
if (managedDeny.length === 0) {
|
|
128
|
+
return settings;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const result = { ...settings };
|
|
132
|
+
const permissions: PermissionRules = { ...(result.permissions || {}) };
|
|
133
|
+
|
|
134
|
+
// Ensure deny list includes all managed deny rules
|
|
135
|
+
const currentDeny = permissions.deny || [];
|
|
136
|
+
permissions.deny = deduplicateArray([...currentDeny, ...managedDeny]);
|
|
137
|
+
|
|
138
|
+
// Remove managed deny patterns from allow list
|
|
139
|
+
if (permissions.allow) {
|
|
140
|
+
permissions.allow = permissions.allow.filter(
|
|
141
|
+
(pattern) => !managedDeny.includes(pattern)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
result.permissions = permissions;
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Merge all configuration sources and apply restrictions
|
|
151
|
+
*
|
|
152
|
+
* This is the main entry point for configuration merging.
|
|
153
|
+
*/
|
|
154
|
+
export function mergeAllSources(sources: ConfigSource[]): MergedConfig {
|
|
155
|
+
// Extract managed deny rules first
|
|
156
|
+
const managedDeny = extractManagedDeny(sources);
|
|
157
|
+
|
|
158
|
+
// Merge all settings
|
|
159
|
+
let settings = mergeSettings(sources);
|
|
160
|
+
|
|
161
|
+
// Apply managed restrictions
|
|
162
|
+
settings = applyManagedRestrictions(settings, managedDeny);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
settings,
|
|
166
|
+
sources,
|
|
167
|
+
managedDeny,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Merge settings with CLI arguments
|
|
173
|
+
*
|
|
174
|
+
* CLI arguments have the highest priority (except managed restrictions).
|
|
175
|
+
*/
|
|
176
|
+
export function mergeWithCliArgs(
|
|
177
|
+
merged: MergedConfig,
|
|
178
|
+
cliArgs: Partial<Settings>
|
|
179
|
+
): MergedConfig {
|
|
180
|
+
// Merge CLI args as a virtual source
|
|
181
|
+
const settings = deepMerge(merged.settings, cliArgs);
|
|
182
|
+
|
|
183
|
+
// Re-apply managed restrictions to ensure they're not overridden
|
|
184
|
+
const finalSettings = applyManagedRestrictions(settings, merged.managedDeny);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...merged,
|
|
188
|
+
settings: finalSettings,
|
|
189
|
+
sources: [
|
|
190
|
+
...merged.sources,
|
|
191
|
+
{
|
|
192
|
+
level: 'cli',
|
|
193
|
+
path: '<cli>',
|
|
194
|
+
namespace: 'gencode',
|
|
195
|
+
settings: cliArgs,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a debug summary of the merge process
|
|
203
|
+
*/
|
|
204
|
+
export function createMergeSummary(merged: MergedConfig): string {
|
|
205
|
+
const lines: string[] = ['Configuration Sources (in priority order):'];
|
|
206
|
+
|
|
207
|
+
for (const source of merged.sources) {
|
|
208
|
+
const marker = source.level === 'managed' ? ' [enforced]' : '';
|
|
209
|
+
lines.push(` ${source.level}:${source.namespace} - ${source.path}${marker}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (merged.managedDeny.length > 0) {
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push('Managed Deny Rules (cannot be overridden):');
|
|
215
|
+
for (const rule of merged.managedDeny) {
|
|
216
|
+
lines.push(` - ${rule}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return lines.join('\n');
|
|
221
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Providers Config Manager - Reads providers.json for model-to-provider mapping
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import type { ProvidersConfig, ProviderName } from './types.js';
|
|
9
|
+
import { DEFAULT_SETTINGS_DIR, PROVIDERS_FILE_NAME } from './types.js';
|
|
10
|
+
|
|
11
|
+
export class ProvidersConfigManager {
|
|
12
|
+
private settingsDir: string;
|
|
13
|
+
private providersPath: string;
|
|
14
|
+
private config: ProvidersConfig | null = null;
|
|
15
|
+
|
|
16
|
+
constructor(settingsDir?: string) {
|
|
17
|
+
const dir = settingsDir ?? DEFAULT_SETTINGS_DIR;
|
|
18
|
+
this.settingsDir = dir.replace('~', os.homedir());
|
|
19
|
+
this.providersPath = path.join(this.settingsDir, PROVIDERS_FILE_NAME);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load providers config from disk
|
|
24
|
+
*/
|
|
25
|
+
async load(): Promise<ProvidersConfig | null> {
|
|
26
|
+
try {
|
|
27
|
+
const content = await fs.readFile(this.providersPath, 'utf-8');
|
|
28
|
+
this.config = JSON.parse(content);
|
|
29
|
+
return this.config;
|
|
30
|
+
} catch {
|
|
31
|
+
// File doesn't exist or is invalid
|
|
32
|
+
this.config = null;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get cached config (call load() first)
|
|
39
|
+
*/
|
|
40
|
+
get(): ProvidersConfig | null {
|
|
41
|
+
return this.config;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Infer provider from model ID using cached models in providers.json
|
|
46
|
+
* Returns undefined if model not found in any provider's cached list
|
|
47
|
+
*/
|
|
48
|
+
inferProvider(modelId: string): ProviderName | undefined {
|
|
49
|
+
if (!this.config?.models) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const [providerKey, providerModels] of Object.entries(this.config.models)) {
|
|
54
|
+
const found = providerModels.list?.some((m) => m.id === modelId);
|
|
55
|
+
if (found) {
|
|
56
|
+
// Map provider key to ProviderName
|
|
57
|
+
// Note: 'anthropic' in providers.json might use vertex connection
|
|
58
|
+
if (providerKey === 'gemini') {
|
|
59
|
+
return 'gemini';
|
|
60
|
+
} else if (providerKey === 'anthropic') {
|
|
61
|
+
// Check connection method to determine if vertex or direct
|
|
62
|
+
const connection = this.config.connections?.[providerKey];
|
|
63
|
+
if (connection?.method === 'vertex') {
|
|
64
|
+
return 'vertex-ai';
|
|
65
|
+
}
|
|
66
|
+
return 'anthropic';
|
|
67
|
+
} else if (providerKey === 'openai') {
|
|
68
|
+
return 'openai';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get all model IDs for a provider
|
|
78
|
+
*/
|
|
79
|
+
getModelIds(provider: string): string[] {
|
|
80
|
+
if (!this.config?.models?.[provider]) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return this.config.models[provider].list?.map((m) => m.id) ?? [];
|
|
84
|
+
}
|
|
85
|
+
}
|