gencode-ai 0.2.0 → 0.3.0

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.
Files changed (150) hide show
  1. package/dist/agent/agent.d.ts +9 -2
  2. package/dist/agent/agent.d.ts.map +1 -1
  3. package/dist/agent/agent.js +37 -8
  4. package/dist/agent/agent.js.map +1 -1
  5. package/dist/agent/types.d.ts +5 -1
  6. package/dist/agent/types.d.ts.map +1 -1
  7. package/dist/cli/components/App.d.ts.map +1 -1
  8. package/dist/cli/components/App.js +15 -9
  9. package/dist/cli/components/App.js.map +1 -1
  10. package/dist/cli/components/Messages.js +1 -1
  11. package/dist/cli/components/Messages.js.map +1 -1
  12. package/dist/cli/components/ModelSelector.d.ts +4 -3
  13. package/dist/cli/components/ModelSelector.d.ts.map +1 -1
  14. package/dist/cli/components/ModelSelector.js +54 -37
  15. package/dist/cli/components/ModelSelector.js.map +1 -1
  16. package/dist/cli/components/ProviderManager.d.ts +2 -2
  17. package/dist/cli/components/ProviderManager.d.ts.map +1 -1
  18. package/dist/cli/components/ProviderManager.js +137 -156
  19. package/dist/cli/components/ProviderManager.js.map +1 -1
  20. package/dist/cli/index.js +30 -13
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/config/index.d.ts +2 -2
  23. package/dist/config/index.d.ts.map +1 -1
  24. package/dist/config/index.js +1 -1
  25. package/dist/config/index.js.map +1 -1
  26. package/dist/config/levels.d.ts +5 -5
  27. package/dist/config/levels.d.ts.map +1 -1
  28. package/dist/config/levels.js +20 -20
  29. package/dist/config/levels.js.map +1 -1
  30. package/dist/config/merger.js +1 -1
  31. package/dist/config/merger.js.map +1 -1
  32. package/dist/config/providers-config.d.ts +8 -5
  33. package/dist/config/providers-config.d.ts.map +1 -1
  34. package/dist/config/providers-config.js +19 -22
  35. package/dist/config/providers-config.js.map +1 -1
  36. package/dist/config/test-utils.d.ts +2 -2
  37. package/dist/config/test-utils.d.ts.map +1 -1
  38. package/dist/config/test-utils.js +4 -4
  39. package/dist/config/test-utils.js.map +1 -1
  40. package/dist/config/types.d.ts +23 -17
  41. package/dist/config/types.d.ts.map +1 -1
  42. package/dist/config/types.js +14 -14
  43. package/dist/config/types.js.map +1 -1
  44. package/dist/memory/memory-manager.d.ts +25 -12
  45. package/dist/memory/memory-manager.d.ts.map +1 -1
  46. package/dist/memory/memory-manager.js +241 -112
  47. package/dist/memory/memory-manager.js.map +1 -1
  48. package/dist/memory/test-utils.d.ts +1 -1
  49. package/dist/memory/test-utils.d.ts.map +1 -1
  50. package/dist/memory/test-utils.js +3 -3
  51. package/dist/memory/test-utils.js.map +1 -1
  52. package/dist/memory/types.d.ts +20 -10
  53. package/dist/memory/types.d.ts.map +1 -1
  54. package/dist/memory/types.js +13 -13
  55. package/dist/memory/types.js.map +1 -1
  56. package/dist/migration/migrate.d.ts +24 -0
  57. package/dist/migration/migrate.d.ts.map +1 -0
  58. package/dist/migration/migrate.js +164 -0
  59. package/dist/migration/migrate.js.map +1 -0
  60. package/dist/permissions/persistence.d.ts +2 -2
  61. package/dist/permissions/persistence.js +4 -4
  62. package/dist/permissions/persistence.js.map +1 -1
  63. package/dist/planning/plan-file.d.ts +1 -1
  64. package/dist/planning/plan-file.js +2 -2
  65. package/dist/planning/plan-file.js.map +1 -1
  66. package/dist/prompts/index.d.ts +5 -4
  67. package/dist/prompts/index.d.ts.map +1 -1
  68. package/dist/prompts/index.js +11 -8
  69. package/dist/prompts/index.js.map +1 -1
  70. package/dist/providers/anthropic.d.ts +2 -1
  71. package/dist/providers/anthropic.d.ts.map +1 -1
  72. package/dist/providers/anthropic.js +7 -0
  73. package/dist/providers/anthropic.js.map +1 -1
  74. package/dist/providers/gemini.d.ts +2 -1
  75. package/dist/providers/gemini.d.ts.map +1 -1
  76. package/dist/providers/gemini.js +7 -0
  77. package/dist/providers/gemini.js.map +1 -1
  78. package/dist/providers/index.d.ts +20 -10
  79. package/dist/providers/index.d.ts.map +1 -1
  80. package/dist/providers/index.js +48 -24
  81. package/dist/providers/index.js.map +1 -1
  82. package/dist/providers/openai.d.ts +2 -1
  83. package/dist/providers/openai.d.ts.map +1 -1
  84. package/dist/providers/openai.js +7 -0
  85. package/dist/providers/openai.js.map +1 -1
  86. package/dist/providers/registry.d.ts +48 -34
  87. package/dist/providers/registry.d.ts.map +1 -1
  88. package/dist/providers/registry.js +72 -88
  89. package/dist/providers/registry.js.map +1 -1
  90. package/dist/providers/store.d.ts +43 -17
  91. package/dist/providers/store.d.ts.map +1 -1
  92. package/dist/providers/store.js +112 -19
  93. package/dist/providers/store.js.map +1 -1
  94. package/dist/providers/types.d.ts +23 -0
  95. package/dist/providers/types.d.ts.map +1 -1
  96. package/dist/providers/vertex-ai.d.ts +15 -7
  97. package/dist/providers/vertex-ai.d.ts.map +1 -1
  98. package/dist/providers/vertex-ai.js +46 -13
  99. package/dist/providers/vertex-ai.js.map +1 -1
  100. package/dist/session/types.js +1 -1
  101. package/dist/session/types.js.map +1 -1
  102. package/docs/config-system-comparison.md +50 -50
  103. package/docs/cost-tracking-comparison.md +2 -2
  104. package/docs/memory-system.md +124 -31
  105. package/docs/permissions.md +2 -2
  106. package/docs/proposals/0006-memory-system.md +4 -4
  107. package/docs/proposals/0008-checkpointing.md +109 -2
  108. package/docs/proposals/0011-custom-commands.md +2 -1
  109. package/docs/proposals/0021-skills-system.md +2 -1
  110. package/docs/proposals/0023-permission-enhancements.md +2 -2
  111. package/docs/proposals/0033-enterprise-deployment.md +1 -1
  112. package/docs/proposals/0041-configuration-system.md +17 -19
  113. package/docs/proposals/0042-prompt-optimization.md +17 -9
  114. package/docs/proposals/README.md +5 -5
  115. package/docs/providers.md +94 -9
  116. package/package.json +3 -2
  117. package/scripts/migrate.ts +449 -0
  118. package/src/agent/agent.ts +51 -9
  119. package/src/agent/types.ts +5 -1
  120. package/src/cli/components/App.tsx +17 -8
  121. package/src/cli/components/Messages.tsx +1 -1
  122. package/src/cli/components/ModelSelector.tsx +62 -43
  123. package/src/cli/components/ProviderManager.tsx +278 -323
  124. package/src/cli/index.tsx +36 -17
  125. package/src/config/index.ts +5 -3
  126. package/src/config/levels.test.ts +22 -22
  127. package/src/config/levels.ts +22 -22
  128. package/src/config/loader.test.ts +14 -14
  129. package/src/config/manager.test.ts +19 -19
  130. package/src/config/merger.test.ts +23 -23
  131. package/src/config/merger.ts +1 -1
  132. package/src/config/providers-config.ts +23 -21
  133. package/src/config/test-utils.ts +6 -6
  134. package/src/config/types.ts +30 -20
  135. package/src/memory/memory-manager.test.ts +242 -24
  136. package/src/memory/memory-manager.ts +270 -141
  137. package/src/memory/test-utils.ts +4 -4
  138. package/src/memory/types.ts +28 -17
  139. package/src/permissions/persistence.ts +4 -4
  140. package/src/planning/plan-file.ts +2 -2
  141. package/src/prompts/index.ts +13 -9
  142. package/src/providers/anthropic.ts +9 -0
  143. package/src/providers/gemini.ts +9 -0
  144. package/src/providers/index.ts +76 -33
  145. package/src/providers/openai.ts +9 -0
  146. package/src/providers/registry.ts +116 -111
  147. package/src/providers/store.ts +130 -28
  148. package/src/providers/types.ts +33 -1
  149. package/src/providers/vertex-ai.ts +49 -13
  150. package/src/session/types.ts +1 -1
@@ -1,23 +1,128 @@
1
1
  /**
2
- * Provider Registry - Provider definitions with connection options
2
+ * Provider Registry - Provider class registry with metadata
3
3
  */
4
4
 
5
- import type { ProviderName } from './index.js';
5
+ import type { Provider, AuthMethod, ProviderClassMeta, LLMProvider } from './types.js';
6
6
  import type { SearchProviderName } from './search/types.js';
7
+ import { AnthropicProvider } from './anthropic.js';
8
+ import { AnthropicVertexProvider } from './vertex-ai.js';
9
+ import { OpenAIProvider } from './openai.js';
10
+ import { GeminiProvider } from './gemini.js';
7
11
 
8
- export interface ConnectionOption {
9
- method: string;
12
+ // ============================================================================
13
+ // LLM Provider Classes
14
+ // ============================================================================
15
+
16
+ export type ProviderClass = {
17
+ new (config?: any): LLMProvider;
18
+ meta: ProviderClassMeta;
19
+ };
20
+
21
+ /**
22
+ * All registered provider classes
23
+ * Each class has static metadata describing which provider and auth method it implements
24
+ */
25
+ export const PROVIDER_CLASSES: ProviderClass[] = [
26
+ AnthropicProvider,
27
+ AnthropicVertexProvider,
28
+ OpenAIProvider,
29
+ GeminiProvider,
30
+ ];
31
+
32
+ /**
33
+ * Provider metadata (for UI display)
34
+ */
35
+ export interface ProviderMeta {
36
+ id: Provider;
10
37
  name: string;
11
- envVars: string[];
12
- description?: string;
13
- providerImpl?: ProviderName; // Override provider implementation (e.g., vertex-ai for Anthropic via GCP)
38
+ popularity: number; // Lower = more popular
39
+ }
40
+
41
+ export const PROVIDER_METADATA: ProviderMeta[] = [
42
+ { id: 'anthropic', name: 'Anthropic', popularity: 1 },
43
+ { id: 'openai', name: 'OpenAI', popularity: 2 },
44
+ { id: 'gemini', name: 'Google Gemini', popularity: 3 },
45
+ ];
46
+
47
+ // ============================================================================
48
+ // Helper Functions
49
+ // ============================================================================
50
+
51
+ /**
52
+ * Get a specific provider class by provider and auth method
53
+ */
54
+ export function getProviderClass(
55
+ provider: Provider,
56
+ authMethod: AuthMethod
57
+ ): ProviderClass | undefined {
58
+ return PROVIDER_CLASSES.find(
59
+ (cls) => cls.meta.provider === provider && cls.meta.authMethod === authMethod
60
+ );
14
61
  }
15
62
 
16
- export interface ProviderDefinition {
17
- id: ProviderName;
63
+ /**
64
+ * Get all provider classes for a given provider
65
+ */
66
+ export function getProviderClasses(provider: Provider): ProviderClass[] {
67
+ return PROVIDER_CLASSES.filter((cls) => cls.meta.provider === provider);
68
+ }
69
+
70
+ /**
71
+ * Check if a provider class is ready (has required env vars)
72
+ */
73
+ export function isProviderReady(providerClass: ProviderClass): boolean {
74
+ // Special case for Vertex AI: requires explicit opt-in
75
+ if (providerClass.meta.authMethod === 'vertex') {
76
+ const useVertex = process.env.CLAUDE_CODE_USE_VERTEX === '1' || process.env.CLAUDE_CODE_USE_VERTEX === 'true';
77
+ const hasProject = !!(
78
+ process.env.ANTHROPIC_VERTEX_PROJECT_ID ||
79
+ process.env.GCLOUD_PROJECT ||
80
+ process.env.GOOGLE_CLOUD_PROJECT
81
+ );
82
+ return useVertex && hasProject;
83
+ }
84
+
85
+ // For other providers: check if any required env var is set
86
+ return providerClass.meta.envVars.some((v) => !!process.env[v]);
87
+ }
88
+
89
+ /**
90
+ * Get provider metadata by ID
91
+ */
92
+ export function getProviderMeta(id: Provider): ProviderMeta | undefined {
93
+ return PROVIDER_METADATA.find((p) => p.id === id);
94
+ }
95
+
96
+ /**
97
+ * Get all providers sorted by popularity
98
+ */
99
+ export function getProvidersSorted(): ProviderMeta[] {
100
+ return [...PROVIDER_METADATA].sort((a, b) => a.popularity - b.popularity);
101
+ }
102
+
103
+ /**
104
+ * Get the first available (ready) provider class for a provider
105
+ */
106
+ export function getAvailableProviderClass(provider: Provider): ProviderClass | undefined {
107
+ const classes = getProviderClasses(provider);
108
+ return classes.find((cls) => isProviderReady(cls));
109
+ }
110
+
111
+ /**
112
+ * Get all available (ready) provider classes for a provider
113
+ */
114
+ export function getAvailableProviderClasses(provider: Provider): ProviderClass[] {
115
+ return getProviderClasses(provider).filter((cls) => isProviderReady(cls));
116
+ }
117
+
118
+ // ============================================================================
119
+ // Legacy Support (for backward compatibility with search providers)
120
+ // ============================================================================
121
+
122
+ export interface ConnectionOption {
18
123
  name: string;
19
- popularity: number; // Lower = more popular, used for sorting
20
- connections: ConnectionOption[];
124
+ envVars: string[];
125
+ description?: string;
21
126
  }
22
127
 
23
128
  export interface SearchProviderDefinition {
@@ -28,64 +133,6 @@ export interface SearchProviderDefinition {
28
133
  requiresKey: boolean;
29
134
  }
30
135
 
31
- /**
32
- * All supported providers with their connection options
33
- */
34
- export const PROVIDERS: ProviderDefinition[] = [
35
- {
36
- id: 'anthropic',
37
- name: 'Anthropic',
38
- popularity: 1,
39
- connections: [
40
- {
41
- method: 'api_key',
42
- name: 'API Key',
43
- envVars: ['ANTHROPIC_API_KEY'],
44
- description: 'Direct API access',
45
- },
46
- {
47
- method: 'vertex',
48
- name: 'Google Vertex AI',
49
- envVars: ['ANTHROPIC_VERTEX_PROJECT_ID', 'GOOGLE_CLOUD_PROJECT'],
50
- description: 'Claude via GCP',
51
- providerImpl: 'vertex-ai',
52
- },
53
- {
54
- method: 'bedrock',
55
- name: 'Amazon Bedrock',
56
- envVars: ['AWS_ACCESS_KEY_ID', 'AWS_PROFILE'],
57
- description: 'Claude via AWS (coming soon)',
58
- },
59
- ],
60
- },
61
- {
62
- id: 'openai',
63
- name: 'OpenAI',
64
- popularity: 2,
65
- connections: [
66
- {
67
- method: 'api_key',
68
- name: 'API Key',
69
- envVars: ['OPENAI_API_KEY'],
70
- description: 'Direct API access',
71
- },
72
- ],
73
- },
74
- {
75
- id: 'gemini',
76
- name: 'Google Gemini',
77
- popularity: 3,
78
- connections: [
79
- {
80
- method: 'api_key',
81
- name: 'API Key',
82
- envVars: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
83
- description: 'Direct API access',
84
- },
85
- ],
86
- },
87
- ];
88
-
89
136
  /**
90
137
  * All supported search providers
91
138
  */
@@ -96,7 +143,6 @@ export const SEARCH_PROVIDERS: SearchProviderDefinition[] = [
96
143
  popularity: 1,
97
144
  connections: [
98
145
  {
99
- method: 'public',
100
146
  name: 'Public API',
101
147
  envVars: [],
102
148
  description: 'No API key required',
@@ -110,7 +156,6 @@ export const SEARCH_PROVIDERS: SearchProviderDefinition[] = [
110
156
  popularity: 2,
111
157
  connections: [
112
158
  {
113
- method: 'api_key',
114
159
  name: 'API Key',
115
160
  envVars: ['SERPER_API_KEY'],
116
161
  description: 'Google Search via Serper',
@@ -124,7 +169,6 @@ export const SEARCH_PROVIDERS: SearchProviderDefinition[] = [
124
169
  popularity: 3,
125
170
  connections: [
126
171
  {
127
- method: 'api_key',
128
172
  name: 'API Key',
129
173
  envVars: ['BRAVE_API_KEY'],
130
174
  description: 'Privacy-focused search',
@@ -134,13 +178,6 @@ export const SEARCH_PROVIDERS: SearchProviderDefinition[] = [
134
178
  },
135
179
  ];
136
180
 
137
- /**
138
- * Get provider definition by ID
139
- */
140
- export function getProvider(id: ProviderName): ProviderDefinition | undefined {
141
- return PROVIDERS.find((p) => p.id === id);
142
- }
143
-
144
181
  /**
145
182
  * Get search provider definition by ID
146
183
  */
@@ -155,44 +192,12 @@ export function getSearchProvidersSorted(): SearchProviderDefinition[] {
155
192
  return [...SEARCH_PROVIDERS].sort((a, b) => a.popularity - b.popularity);
156
193
  }
157
194
 
158
- /**
159
- * Get all providers sorted by popularity
160
- */
161
- export function getProvidersSorted(): ProviderDefinition[] {
162
- return [...PROVIDERS].sort((a, b) => a.popularity - b.popularity);
163
- }
164
-
165
195
  // Helper: check if any env var in the list is set
166
196
  const hasAnyEnvVar = (envVars: string[]) => envVars.some((v) => !!process.env[v]);
167
197
 
168
- /**
169
- * Check if any of the provider's env vars are set
170
- */
171
- export function hasEnvVars(provider: ProviderDefinition): boolean {
172
- return provider.connections.some((conn) => hasAnyEnvVar(conn.envVars));
173
- }
174
-
175
- /**
176
- * Get the first available connection method (where env vars are set)
177
- */
178
- export function getAvailableConnection(
179
- provider: ProviderDefinition
180
- ): ConnectionOption | undefined {
181
- return provider.connections.find((conn) => hasAnyEnvVar(conn.envVars));
182
- }
183
-
184
198
  /**
185
199
  * Check if a specific connection option has its env vars set
186
200
  */
187
201
  export function isConnectionReady(conn: ConnectionOption): boolean {
188
202
  return hasAnyEnvVar(conn.envVars);
189
203
  }
190
-
191
- /**
192
- * Get all available (ready) connections for a provider
193
- */
194
- export function getAvailableConnections(
195
- provider: ProviderDefinition
196
- ): ConnectionOption[] {
197
- return provider.connections.filter((conn) => hasAnyEnvVar(conn.envVars));
198
- }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Provider Store - Manages provider connections and model cache
3
3
  *
4
- * Storage location: ~/.gencode/providers.json
4
+ * Storage location: ~/.gen/providers.json
5
5
  */
6
6
 
7
7
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
8
8
  import { join } from 'path';
9
9
  import { homedir } from 'os';
10
- import type { ProviderName } from './index.js';
10
+ import type { Provider, AuthMethod } from './types.js';
11
11
  import type { SearchProviderName } from './search/types.js';
12
12
 
13
13
  export interface ModelInfo {
@@ -16,7 +16,8 @@ export interface ModelInfo {
16
16
  }
17
17
 
18
18
  export interface ProviderConnection {
19
- method: string;
19
+ authMethod: AuthMethod; // Authentication method
20
+ method?: string; // Legacy: Connection name (e.g., "Direct API", "Google Vertex AI")
20
21
  connectedAt: string;
21
22
  }
22
23
 
@@ -31,9 +32,28 @@ export interface ProvidersConfig {
31
32
  searchProvider?: SearchProviderName;
32
33
  }
33
34
 
34
- const CONFIG_DIR = join(homedir(), '.gencode');
35
+ const CONFIG_DIR = join(homedir(), '.gen');
35
36
  const CONFIG_FILE = join(CONFIG_DIR, 'providers.json');
36
37
 
38
+ /**
39
+ * Generate model cache key from provider and authMethod
40
+ */
41
+ export function getModelCacheKey(provider: Provider, authMethod: AuthMethod): string {
42
+ return `${provider}:${authMethod}`;
43
+ }
44
+
45
+ /**
46
+ * Parse model cache key to extract provider and authMethod
47
+ */
48
+ export function parseModelCacheKey(key: string): { provider: Provider; authMethod: AuthMethod } | null {
49
+ const parts = key.split(':');
50
+ if (parts.length !== 2) return null;
51
+ return {
52
+ provider: parts[0] as Provider,
53
+ authMethod: parts[1] as AuthMethod,
54
+ };
55
+ }
56
+
37
57
  /**
38
58
  * Provider Store - manages connection state and model cache
39
59
  */
@@ -76,67 +96,93 @@ export class ProviderStore {
76
96
  /**
77
97
  * Check if a provider is connected
78
98
  */
79
- isConnected(providerId: ProviderName): boolean {
99
+ isConnected(providerId: Provider): boolean {
80
100
  return !!this.config.connections[providerId];
81
101
  }
82
102
 
83
103
  /**
84
104
  * Get connection info for a provider
85
105
  */
86
- getConnection(providerId: ProviderName): ProviderConnection | undefined {
106
+ getConnection(providerId: Provider): ProviderConnection | undefined {
87
107
  return this.config.connections[providerId];
88
108
  }
89
109
 
90
110
  /**
91
111
  * Get all connected provider IDs
92
112
  */
93
- getConnectedProviders(): ProviderName[] {
94
- return Object.keys(this.config.connections) as ProviderName[];
113
+ getConnectedProviders(): Provider[] {
114
+ return Object.keys(this.config.connections) as Provider[];
95
115
  }
96
116
 
97
117
  /**
98
- * Connect a provider
118
+ * Connect a provider with auth method
99
119
  */
100
- connect(providerId: ProviderName, method: string): void {
120
+ connect(providerId: Provider, authMethod: AuthMethod, displayName?: string): void {
101
121
  this.config.connections[providerId] = {
102
- method,
122
+ authMethod,
123
+ method: displayName, // Optional legacy field for display
103
124
  connectedAt: new Date().toISOString(),
104
125
  };
105
126
  this.save();
106
127
  }
107
128
 
108
129
  /**
109
- * Disconnect a provider
130
+ * Disconnect a provider and clear all its model caches
110
131
  */
111
- disconnect(providerId: ProviderName): void {
132
+ disconnect(providerId: Provider): void {
112
133
  delete this.config.connections[providerId];
113
- delete this.config.models[providerId];
134
+
135
+ // Remove all model caches for this provider (across all authMethods)
136
+ for (const key of Object.keys(this.config.models)) {
137
+ const parsed = parseModelCacheKey(key);
138
+ if (parsed && parsed.provider === providerId) {
139
+ delete this.config.models[key];
140
+ }
141
+ }
142
+
114
143
  this.save();
115
144
  }
116
145
 
117
146
  /**
118
147
  * Get cached models for a provider
148
+ * @param providerId Provider ID
149
+ * @param authMethod Optional: specific auth method. If not provided, returns all models for the provider
119
150
  */
120
- getModels(providerId: ProviderName): ModelInfo[] {
121
- return this.config.models[providerId]?.list ?? [];
151
+ getModels(providerId: Provider, authMethod?: AuthMethod): ModelInfo[] {
152
+ if (authMethod) {
153
+ // Get models for specific authMethod
154
+ const key = getModelCacheKey(providerId, authMethod);
155
+ return this.config.models[key]?.list ?? [];
156
+ }
157
+
158
+ // Get all models for this provider (across all authMethods)
159
+ const allModels: ModelInfo[] = [];
160
+ for (const [key, cache] of Object.entries(this.config.models)) {
161
+ const parsed = parseModelCacheKey(key);
162
+ if (parsed && parsed.provider === providerId) {
163
+ allModels.push(...cache.list);
164
+ }
165
+ }
166
+ return allModels;
122
167
  }
123
168
 
124
169
  /**
125
170
  * Get all cached models grouped by provider
126
171
  */
127
- getAllModels(): Record<ProviderName, ModelInfo[]> {
172
+ getAllModels(): Record<Provider, ModelInfo[]> {
128
173
  const result: Record<string, ModelInfo[]> = {};
129
174
  for (const [providerId, cache] of Object.entries(this.config.models)) {
130
175
  result[providerId] = cache.list;
131
176
  }
132
- return result as Record<ProviderName, ModelInfo[]>;
177
+ return result as Record<Provider, ModelInfo[]>;
133
178
  }
134
179
 
135
180
  /**
136
- * Cache models for a provider
181
+ * Cache models for a provider with auth method
137
182
  */
138
- cacheModels(providerId: ProviderName, models: ModelInfo[]): void {
139
- this.config.models[providerId] = {
183
+ cacheModels(providerId: Provider, authMethod: AuthMethod, models: ModelInfo[]): void {
184
+ const key = getModelCacheKey(providerId, authMethod);
185
+ this.config.models[key] = {
140
186
  cachedAt: new Date().toISOString(),
141
187
  list: models,
142
188
  };
@@ -145,17 +191,37 @@ export class ProviderStore {
145
191
 
146
192
  /**
147
193
  * Get cache timestamp for a provider
194
+ * @param providerId Provider ID
195
+ * @param authMethod Optional: specific auth method. If not provided, returns latest cache time
148
196
  */
149
- getCacheTime(providerId: ProviderName): Date | undefined {
150
- const cache = this.config.models[providerId];
151
- return cache ? new Date(cache.cachedAt) : undefined;
197
+ getCacheTime(providerId: Provider, authMethod?: AuthMethod): Date | undefined {
198
+ if (authMethod) {
199
+ const key = getModelCacheKey(providerId, authMethod);
200
+ const cache = this.config.models[key];
201
+ return cache ? new Date(cache.cachedAt) : undefined;
202
+ }
203
+
204
+ // Get latest cache time across all authMethods for this provider
205
+ let latestTime: Date | undefined;
206
+ for (const [key, cache] of Object.entries(this.config.models)) {
207
+ const parsed = parseModelCacheKey(key);
208
+ if (parsed && parsed.provider === providerId) {
209
+ const cacheTime = new Date(cache.cachedAt);
210
+ if (!latestTime || cacheTime > latestTime) {
211
+ latestTime = cacheTime;
212
+ }
213
+ }
214
+ }
215
+ return latestTime;
152
216
  }
153
217
 
154
218
  /**
155
219
  * Check if model cache is stale (older than 24 hours)
220
+ * @param providerId Provider ID
221
+ * @param authMethod Optional: specific auth method. If not provided, checks if any cache is fresh
156
222
  */
157
- isCacheStale(providerId: ProviderName): boolean {
158
- const cacheTime = this.getCacheTime(providerId);
223
+ isCacheStale(providerId: Provider, authMethod?: AuthMethod): boolean {
224
+ const cacheTime = this.getCacheTime(providerId, authMethod);
159
225
  if (!cacheTime) return true;
160
226
  const hoursSinceCache = (Date.now() - cacheTime.getTime()) / (1000 * 60 * 60);
161
227
  return hoursSinceCache > 24;
@@ -173,9 +239,45 @@ export class ProviderStore {
173
239
 
174
240
  /**
175
241
  * Get model count for a specific provider
242
+ * @param providerId Provider ID
243
+ * @param authMethod Optional: specific auth method. If not provided, counts all models for the provider
244
+ */
245
+ getModelCount(providerId: Provider, authMethod?: AuthMethod): number {
246
+ if (authMethod) {
247
+ const key = getModelCacheKey(providerId, authMethod);
248
+ return this.config.models[key]?.list.length ?? 0;
249
+ }
250
+
251
+ // Count all models for this provider (across all authMethods)
252
+ let count = 0;
253
+ for (const [key, cache] of Object.entries(this.config.models)) {
254
+ const parsed = parseModelCacheKey(key);
255
+ if (parsed && parsed.provider === providerId) {
256
+ count += cache.list.length;
257
+ }
258
+ }
259
+ return count;
260
+ }
261
+
262
+ /**
263
+ * Get the model cache info
264
+ * @param providerId Provider ID
265
+ * @param authMethod Optional: specific auth method
176
266
  */
177
- getModelCount(providerId: ProviderName): number {
178
- return this.config.models[providerId]?.list.length ?? 0;
267
+ getModelCache(providerId: Provider, authMethod?: AuthMethod): ModelCache | undefined {
268
+ if (authMethod) {
269
+ const key = getModelCacheKey(providerId, authMethod);
270
+ return this.config.models[key];
271
+ }
272
+
273
+ // If no authMethod specified, try to find any cache for this provider
274
+ for (const [key, cache] of Object.entries(this.config.models)) {
275
+ const parsed = parseModelCacheKey(key);
276
+ if (parsed && parsed.provider === providerId) {
277
+ return cache;
278
+ }
279
+ }
280
+ return undefined;
179
281
  }
180
282
 
181
283
  /**
@@ -5,6 +5,36 @@
5
5
 
6
6
  import type { CostEstimate } from '../pricing/types.js';
7
7
 
8
+ // ============================================================================
9
+ // Provider Types
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Provider - Semantic layer (only 3 providers)
14
+ */
15
+ export type Provider = 'anthropic' | 'openai' | 'gemini';
16
+
17
+ /**
18
+ * Authentication method for providers
19
+ */
20
+ export type AuthMethod = 'api_key' | 'vertex' | 'bedrock' | 'azure' | 'oauth';
21
+
22
+ /**
23
+ * Provider class metadata (static property on each provider implementation)
24
+ */
25
+ export interface ProviderClassMeta {
26
+ /** Which provider this class implements */
27
+ provider: Provider;
28
+ /** Authentication method */
29
+ authMethod: AuthMethod;
30
+ /** Environment variables required for this auth method */
31
+ envVars: string[];
32
+ /** Display name for UI */
33
+ displayName: string;
34
+ /** Optional description */
35
+ description?: string;
36
+ }
37
+
8
38
  // ============================================================================
9
39
  // Message Types
10
40
  // ============================================================================
@@ -104,7 +134,9 @@ export interface CompletionResponse {
104
134
  export interface StreamChunkText {
105
135
  type: 'text';
106
136
  text: string;
107
- }
137
+ }/* The above code appears to be a comment block in TypeScript. It includes a placeholder
138
+ text "ANTHROPIC_VERTEX_PROJECT_ID" and " */
139
+
108
140
 
109
141
  export interface StreamChunkToolStart {
110
142
  type: 'tool_start';
@@ -1,10 +1,17 @@
1
1
  /**
2
- * Google Vertex AI Provider Implementation
3
- * Supports Claude models deployed on Google Cloud Vertex AI
2
+ * Anthropic Vertex AI Provider
3
+ * Supports Claude models via Google Cloud Vertex AI
4
+ *
5
+ * Documentation: https://code.claude.com/docs/en/google-vertex-ai
6
+ *
7
+ * Required environment variables:
8
+ * - CLAUDE_CODE_USE_VERTEX=1
9
+ * - ANTHROPIC_VERTEX_PROJECT_ID=your-project-id
10
+ * - CLOUD_ML_REGION=global (or specific region like us-east5)
4
11
  *
5
12
  * Authentication uses Google Cloud's default credential chain:
6
- * 1. GOOGLE_APPLICATION_CREDENTIALS (service account JSON)
7
- * 2. gcloud auth application-default login (ADC)
13
+ * 1. gcloud auth application-default login (recommended)
14
+ * 2. GOOGLE_APPLICATION_CREDENTIALS (service account JSON)
8
15
  * 3. GCE/GKE metadata service (when running on GCP)
9
16
  */
10
17
 
@@ -12,6 +19,7 @@ import { GoogleAuth } from 'google-auth-library';
12
19
  import { calculateCost } from '../pricing/calculator.js';
13
20
  import type {
14
21
  LLMProvider,
22
+ ProviderClassMeta,
15
23
  CompletionOptions,
16
24
  CompletionResponse,
17
25
  StreamChunk,
@@ -129,23 +137,40 @@ type StreamEvent =
129
137
  | { type: 'message_stop' }
130
138
  | { type: 'ping' };
131
139
 
132
- export class VertexAIProvider implements LLMProvider {
133
- readonly name = 'vertex-ai';
140
+ export class AnthropicVertexProvider implements LLMProvider {
141
+ static readonly meta: ProviderClassMeta = {
142
+ provider: 'anthropic',
143
+ authMethod: 'vertex',
144
+ envVars: [
145
+ 'CLAUDE_CODE_USE_VERTEX',
146
+ 'ANTHROPIC_VERTEX_PROJECT_ID',
147
+ 'CLOUD_ML_REGION',
148
+ ],
149
+ displayName: 'Google Vertex AI',
150
+ description: 'Claude via Google Cloud Platform',
151
+ };
152
+
153
+ readonly name = 'anthropic-vertex';
134
154
  private projectId: string;
135
155
  private region: string;
136
156
  private auth: GoogleAuth;
137
157
  private accessToken?: string;
138
158
 
139
159
  constructor(config: VertexAIConfig = {}) {
160
+ // Project ID priority (per Claude Code docs):
161
+ // 1. config.projectId
162
+ // 2. ANTHROPIC_VERTEX_PROJECT_ID
163
+ // 3. GCLOUD_PROJECT
164
+ // 4. GOOGLE_CLOUD_PROJECT
140
165
  this.projectId =
141
166
  config.projectId ??
142
167
  process.env.ANTHROPIC_VERTEX_PROJECT_ID ??
168
+ process.env.GCLOUD_PROJECT ??
143
169
  process.env.GOOGLE_CLOUD_PROJECT ??
144
170
  '';
145
171
 
146
172
  this.region =
147
173
  config.region ??
148
- process.env.ANTHROPIC_VERTEX_REGION ??
149
174
  process.env.CLOUD_ML_REGION ??
150
175
  'us-east5';
151
176
 
@@ -153,7 +178,7 @@ export class VertexAIProvider implements LLMProvider {
153
178
 
154
179
  if (!this.projectId) {
155
180
  throw new Error(
156
- 'Vertex AI requires a project ID. Set ANTHROPIC_VERTEX_PROJECT_ID environment variable.'
181
+ 'Vertex AI requires a project ID. Set one of: ANTHROPIC_VERTEX_PROJECT_ID, GCLOUD_PROJECT, or GOOGLE_CLOUD_PROJECT'
157
182
  );
158
183
  }
159
184
 
@@ -167,12 +192,23 @@ export class VertexAIProvider implements LLMProvider {
167
192
  return this.accessToken;
168
193
  }
169
194
 
170
- const client = await this.auth.getClient();
171
- const tokenResponse = await client.getAccessToken();
172
- if (!tokenResponse.token) {
173
- throw new Error('Failed to get access token from Google Cloud');
195
+ try {
196
+ const client = await this.auth.getClient();
197
+ const tokenResponse = await client.getAccessToken();
198
+ if (!tokenResponse.token) {
199
+ throw new Error('Failed to get access token from Google Cloud');
200
+ }
201
+ return tokenResponse.token;
202
+ } catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ throw new Error(
205
+ `Failed to authenticate with Google Cloud. Please ensure you have:\n` +
206
+ `1. Run: gcloud auth application-default login\n` +
207
+ `2. Set ANTHROPIC_VERTEX_PROJECT_ID or GOOGLE_CLOUD_PROJECT\n` +
208
+ `3. Enabled Vertex AI API in your GCP project\n` +
209
+ `Original error: ${message}`
210
+ );
174
211
  }
175
- return tokenResponse.token;
176
212
  }
177
213
 
178
214
  private getEndpoint(model: string, stream: boolean = false): string {