nova-terminal-assistant 0.1.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.
Potentially problematic release.
This version of nova-terminal-assistant might be problematic. Click here for more details.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- package/src/core/utils/index.ts +19 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ModelValidator - Validates model availability and provider configuration
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import fetch from 'node-fetch';
|
|
6
|
+
import { AuthManager } from '../auth/AuthManager.js';
|
|
7
|
+
|
|
8
|
+
export interface ProviderValidationResult {
|
|
9
|
+
isConfigured: boolean;
|
|
10
|
+
isAccessible: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
models: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ModelValidationResult {
|
|
16
|
+
provider: string;
|
|
17
|
+
modelId: string;
|
|
18
|
+
isValid: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
displayName?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class ModelValidator {
|
|
24
|
+
private authManager: any;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate if a provider is properly configured and accessible
|
|
28
|
+
*/
|
|
29
|
+
async validateProvider(providerName: string): Promise<ProviderValidationResult> {
|
|
30
|
+
// Check if provider is configured
|
|
31
|
+
const isConfigured = this.authManager.hasCredentials(providerName);
|
|
32
|
+
if (!isConfigured) {
|
|
33
|
+
return {
|
|
34
|
+
isConfigured: false,
|
|
35
|
+
isAccessible: false,
|
|
36
|
+
error: `Provider "${providerName}" not configured`,
|
|
37
|
+
models: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
switch (providerName.toLowerCase()) {
|
|
43
|
+
case 'ollama':
|
|
44
|
+
return await this.validateOllama();
|
|
45
|
+
case 'ollama-cloud':
|
|
46
|
+
return await this.validateOllamaCloud();
|
|
47
|
+
default:
|
|
48
|
+
return await this.validateApiProvider(providerName);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
isConfigured: true,
|
|
53
|
+
isAccessible: false,
|
|
54
|
+
error: `Failed to connect to ${providerName}: ${(error as Error).message}`,
|
|
55
|
+
models: []
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate a specific model for a provider
|
|
62
|
+
*/
|
|
63
|
+
async validateModel(providerName: string, modelId: string): Promise<ModelValidationResult> {
|
|
64
|
+
try {
|
|
65
|
+
const providerResult = await this.validateProvider(providerName);
|
|
66
|
+
|
|
67
|
+
if (!providerResult.isAccessible) {
|
|
68
|
+
return {
|
|
69
|
+
provider: providerName,
|
|
70
|
+
modelId,
|
|
71
|
+
isValid: false,
|
|
72
|
+
error: providerResult.error
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// For API providers, basic validation is enough
|
|
77
|
+
if (this.isApiProvider(providerName)) {
|
|
78
|
+
return {
|
|
79
|
+
provider: providerName,
|
|
80
|
+
modelId,
|
|
81
|
+
isValid: true,
|
|
82
|
+
displayName: modelId
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// For Ollama, check if model exists locally
|
|
87
|
+
if (providerName === 'ollama') {
|
|
88
|
+
return await this.validateOllamaModel(modelId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
provider: providerName,
|
|
93
|
+
modelId,
|
|
94
|
+
isValid: true,
|
|
95
|
+
displayName: modelId
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
provider: providerName,
|
|
101
|
+
modelId,
|
|
102
|
+
isValid: false,
|
|
103
|
+
error: `Validation failed: ${(error as Error).message}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get all available and validated models for all configured providers
|
|
110
|
+
*/
|
|
111
|
+
async getValidatedModels(): Promise<Map<string, ModelValidationResult[]>> {
|
|
112
|
+
const result = new Map<string, ModelValidationResult[]>();
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Get all providers from config
|
|
116
|
+
const { ConfigManager } = await import('../../../core/src/config/ConfigManager.js');
|
|
117
|
+
const config = ConfigManager.getInstance ? ConfigManager.getInstance() : new ConfigManager();
|
|
118
|
+
const providers = Object.keys(config.getConfig().models.providers);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(`⚠️ Could not load config: ${(error as Error).message}`);
|
|
121
|
+
return result; // Return empty map on error
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const providerName of providers) {
|
|
125
|
+
const providerResult = await this.validateProvider(providerName);
|
|
126
|
+
|
|
127
|
+
if (providerResult.isAccessible && providerResult.models.length > 0) {
|
|
128
|
+
const models: ModelValidationResult[] = [];
|
|
129
|
+
|
|
130
|
+
// Validate each model in the provider
|
|
131
|
+
for (const modelId of providerResult.models) {
|
|
132
|
+
const modelResult = await this.validateModel(providerName, modelId);
|
|
133
|
+
if (modelResult.isValid) {
|
|
134
|
+
models.push(modelResult);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (models.length > 0) {
|
|
139
|
+
result.set(providerName, models);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Quick check if a provider is ready for use
|
|
149
|
+
*/
|
|
150
|
+
isProviderReady(providerName: string): boolean {
|
|
151
|
+
if (!this.authManager.hasCredentials(providerName)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Basic checks for common providers
|
|
156
|
+
switch (providerName.toLowerCase()) {
|
|
157
|
+
case 'ollama':
|
|
158
|
+
const ollamaCreds = this.authManager.getCredentials('ollama');
|
|
159
|
+
return !!ollamaCreds?.baseUrl || process.env.OLLAMA_HOST !== undefined;
|
|
160
|
+
case 'custom':
|
|
161
|
+
const customCreds = this.authManager.getCredentials('custom');
|
|
162
|
+
return !!(customCreds?.apiKey && customCreds?.baseUrl);
|
|
163
|
+
default:
|
|
164
|
+
// API providers just need an API key
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private async validateOllama(): Promise<ProviderValidationResult> {
|
|
170
|
+
const baseUrl = this.authManager.getCredentials('ollama')?.baseUrl ||
|
|
171
|
+
process.env.OLLAMA_HOST ||
|
|
172
|
+
'http://localhost:11434';
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const response = await fetch(`${baseUrl}/api/tags`, {
|
|
176
|
+
method: 'GET',
|
|
177
|
+
timeout: 3000
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
return {
|
|
182
|
+
isConfigured: true,
|
|
183
|
+
isAccessible: false,
|
|
184
|
+
error: `Ollama server returned ${response.status}: ${response.statusText}`,
|
|
185
|
+
models: []
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const data = await response.json() as { models: Array<{ name: string }> };
|
|
190
|
+
const modelNames = data.models.map(m => m.name);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
isConfigured: true,
|
|
194
|
+
isAccessible: true,
|
|
195
|
+
models: modelNames
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return {
|
|
200
|
+
isConfigured: true,
|
|
201
|
+
isAccessible: false,
|
|
202
|
+
error: `Cannot connect to Ollama at ${baseUrl}: ${(error as Error).message}`,
|
|
203
|
+
models: []
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async validateOllamaCloud(): Promise<ProviderValidationResult> {
|
|
209
|
+
const apiKey = this.authManager.getCredentials('ollama-cloud')?.apiKey ||
|
|
210
|
+
process.env.OLLAMA_API_KEY;
|
|
211
|
+
|
|
212
|
+
if (!apiKey) {
|
|
213
|
+
return {
|
|
214
|
+
isConfigured: false,
|
|
215
|
+
isAccessible: false,
|
|
216
|
+
error: 'Ollama Cloud API key not found',
|
|
217
|
+
models: []
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const baseUrl = this.authManager.getCredentials('ollama-cloud')?.baseUrl ||
|
|
223
|
+
'https://ollama.com';
|
|
224
|
+
|
|
225
|
+
const response = await fetch(`${baseUrl}/api/tags`, {
|
|
226
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
227
|
+
timeout: 5000
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
return {
|
|
232
|
+
isConfigured: true,
|
|
233
|
+
isAccessible: false,
|
|
234
|
+
error: `Ollama Cloud returned ${response.status}: ${response.statusText}`,
|
|
235
|
+
models: []
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const data = await response.json() as { models: Array<{ name: string }> };
|
|
240
|
+
const modelNames = data.models.map(m => m.name);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
isConfigured: true,
|
|
244
|
+
isAccessible: true,
|
|
245
|
+
models: modelNames
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
isConfigured: true,
|
|
251
|
+
isAccessible: false,
|
|
252
|
+
error: `Cannot connect to Ollama Cloud: ${(error as Error).message}`,
|
|
253
|
+
models: []
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private async validateApiProvider(providerName: string): Promise<ProviderValidationResult> {
|
|
259
|
+
// For API providers, we can't list models without making expensive calls
|
|
260
|
+
// So we return basic info and let the user try to use it
|
|
261
|
+
const creds = this.authManager.getCredentials(providerName);
|
|
262
|
+
|
|
263
|
+
if (!creds?.apiKey) {
|
|
264
|
+
return {
|
|
265
|
+
isConfigured: false,
|
|
266
|
+
isAccessible: false,
|
|
267
|
+
error: `API key not found for ${providerName}`,
|
|
268
|
+
models: []
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// For now, return empty models array - actual model validation happens on use
|
|
273
|
+
return {
|
|
274
|
+
isConfigured: true,
|
|
275
|
+
isAccessible: true,
|
|
276
|
+
models: [] // Will be populated when actually used
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private async validateOllamaModel(modelId: string): Promise<ModelValidationResult> {
|
|
281
|
+
try {
|
|
282
|
+
const validator = new ModelValidator();
|
|
283
|
+
const ollamaResult = await validator.validateOllama();
|
|
284
|
+
|
|
285
|
+
if (!ollamaResult.isAccessible) {
|
|
286
|
+
return {
|
|
287
|
+
provider: 'ollama',
|
|
288
|
+
modelId,
|
|
289
|
+
isValid: false,
|
|
290
|
+
error: ollamaResult.error
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const modelExists = ollamaResult.models.includes(modelId);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
provider: 'ollama',
|
|
298
|
+
modelId,
|
|
299
|
+
isValid: modelExists,
|
|
300
|
+
displayName: modelExists ? modelId : undefined,
|
|
301
|
+
error: modelExists ? undefined : `Model "${modelId}" not found locally`
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
provider: 'ollama',
|
|
307
|
+
modelId,
|
|
308
|
+
isValid: false,
|
|
309
|
+
error: `Failed to validate Ollama model: ${(error as Error).message}`
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private isApiProvider(providerName: string): boolean {
|
|
315
|
+
const apiProviders = new Set([
|
|
316
|
+
'anthropic', 'openai', 'azure', 'google', 'deepseek',
|
|
317
|
+
'qwen', 'glm', 'moonshot', 'baichuan', 'minimax',
|
|
318
|
+
'yi', 'groq', 'mistral', 'together', 'perplexity',
|
|
319
|
+
'coding-plan-alibaba', 'coding-plan-tencent',
|
|
320
|
+
'coding-plan-volcengine', 'coding-plan-baidu',
|
|
321
|
+
'coding-plan-kimi', 'coding-plan-zhipu', 'coding-plan-minimax'
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
return apiProviders.has(providerName.toLowerCase());
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private getEnvKeyName(provider: string): string {
|
|
328
|
+
const envMap: Record<string, string> = {
|
|
329
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
330
|
+
openai: 'OPENAI_API_KEY',
|
|
331
|
+
google: 'GOOGLE_API_KEY',
|
|
332
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
333
|
+
qwen: 'QWEN_API_KEY',
|
|
334
|
+
glm: 'GLM_API_KEY',
|
|
335
|
+
moonshot: 'MOONSHOT_API_KEY',
|
|
336
|
+
baichuan: 'BAICHUAN_API_KEY',
|
|
337
|
+
minimax: 'MINIMAX_API_KEY',
|
|
338
|
+
yi: 'YI_API_KEY',
|
|
339
|
+
groq: 'GROQ_API_KEY',
|
|
340
|
+
mistral: 'MISTRAL_API_KEY',
|
|
341
|
+
together: 'TOGETHER_API_KEY',
|
|
342
|
+
perplexity: 'PERPLEXITY_API_KEY',
|
|
343
|
+
'ollama-cloud': 'OLLAMA_API_KEY'
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
return envMap[provider] || `${provider.toUpperCase()}_API_KEY`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AACjE,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ModelClient } from './ModelClient.js';
|
|
2
|
+
export type { CreateModelClientOptions } from './ModelClient.js';
|
|
3
|
+
export { ModelConnectionTester } from './ModelConnectionTester.js';
|
|
4
|
+
export type { ModelConnectionStatus, ProviderConnectionStatus } from './ModelConnectionTester.js';
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export * from './providers/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnthropicProvider.d.ts","sourceRoot":"","sources":["AnthropicProvider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAA8D,MAAM,wBAAwB,CAAC;AAElH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAc,WAAW,EAAE,MAAM,aAAa,CAAC;AAK9G,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,IAAI,eAAe;IAC5B,OAAO,CAAC,MAAM,CAAY;gBAEd,MAAM,EAAE,uBAAuB;IAQrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IAsClF,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAAC,WAAW,CAAC;IAoEvF,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAWvD,OAAO,CAAC,eAAe;IAsCvB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,iBAAiB;CAS1B"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Anthropic Provider - Claude API integration
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
6
|
+
import type { Message, ContentBlock, ToolUseContent, ToolResultContent, SessionId } from '../../types/session.js';
|
|
7
|
+
import type { ToolDefinition } from '../../types/tools.js';
|
|
8
|
+
import type { ModelProvider, ModelRequestOptions, ModelResponse, TokenUsage, StreamEvent } from '../types.js';
|
|
9
|
+
import { ModelError, RateLimitError } from '../../types/errors.js';
|
|
10
|
+
import { createToolCallId, createMessageId } from '../../types/session.js';
|
|
11
|
+
import { tokenCounter } from '../../utils/TokenCounter.js';
|
|
12
|
+
|
|
13
|
+
export interface AnthropicProviderConfig {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
model: string;
|
|
17
|
+
apiVersion?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class AnthropicProvider implements ModelProvider {
|
|
21
|
+
readonly name = 'Anthropic';
|
|
22
|
+
private client: Anthropic;
|
|
23
|
+
private enableCache: boolean;
|
|
24
|
+
|
|
25
|
+
constructor(config: AnthropicProviderConfig & { enableCache?: boolean }) {
|
|
26
|
+
this.client = new Anthropic({
|
|
27
|
+
apiKey: config.apiKey,
|
|
28
|
+
baseURL: config.baseUrl,
|
|
29
|
+
...(config.apiVersion && { defaultHeaders: { 'anthropic-version': config.apiVersion } }),
|
|
30
|
+
});
|
|
31
|
+
this.enableCache = config.enableCache !== false; // Default to true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async complete(messages: Message[], options: ModelRequestOptions): Promise<ModelResponse> {
|
|
35
|
+
try {
|
|
36
|
+
const anthropicMessages = this.convertMessages(messages);
|
|
37
|
+
|
|
38
|
+
// Convert tools with cache control if caching is enabled
|
|
39
|
+
const anthropicTools = this.convertTools(options.tools);
|
|
40
|
+
|
|
41
|
+
// Prepare params
|
|
42
|
+
const params: any = {
|
|
43
|
+
model: options.model,
|
|
44
|
+
max_tokens: options.maxTokens,
|
|
45
|
+
messages: anthropicMessages,
|
|
46
|
+
...(anthropicTools.length > 0 && { tools: anthropicTools }),
|
|
47
|
+
...(options.stopSequences && options.stopSequences.length > 0 && { stop_sequences: options.stopSequences }),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Add system prompt with cache control if enabled
|
|
51
|
+
if (options.systemPrompt) {
|
|
52
|
+
if (this.enableCache && options.thinking !== 'disabled') {
|
|
53
|
+
params.system = [
|
|
54
|
+
{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: options.systemPrompt,
|
|
57
|
+
cache_control: { type: 'ephemeral' }
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
} else {
|
|
61
|
+
params.system = options.systemPrompt;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Add beta header for caching
|
|
66
|
+
const requestOptions: any = {};
|
|
67
|
+
if (this.enableCache && options.thinking !== 'disabled') {
|
|
68
|
+
requestOptions.headers = {
|
|
69
|
+
'anthropic-beta': 'prompt-caching-2024-07-31'
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const response = await this.client.messages.create(params, requestOptions);
|
|
74
|
+
|
|
75
|
+
const content = this.convertResponseContent(response.content);
|
|
76
|
+
const usage = this.convertUsage(response.usage);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
content,
|
|
80
|
+
model: response.model,
|
|
81
|
+
stopReason: this.convertStopReason(response.stop_reason),
|
|
82
|
+
usage,
|
|
83
|
+
sessionId: options.sessionId,
|
|
84
|
+
};
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err instanceof Anthropic.APIError) {
|
|
87
|
+
if (err.status === 429) {
|
|
88
|
+
const retryAfter = parseInt(err.headers?.['retry-after'] || '60', 10) * 1000;
|
|
89
|
+
throw new RateLimitError(err.message, retryAfter, 'anthropic');
|
|
90
|
+
}
|
|
91
|
+
throw new ModelError(err.message, err.status, 'anthropic', err);
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async *stream(messages: Message[], options: ModelRequestOptions): AsyncGenerator<StreamEvent> {
|
|
98
|
+
try {
|
|
99
|
+
const anthropicMessages = this.convertMessages(messages);
|
|
100
|
+
|
|
101
|
+
// Convert tools with cache control if caching is enabled
|
|
102
|
+
const anthropicTools = this.convertTools(options.tools);
|
|
103
|
+
|
|
104
|
+
// Prepare params
|
|
105
|
+
const params: any = {
|
|
106
|
+
model: options.model,
|
|
107
|
+
max_tokens: options.maxTokens,
|
|
108
|
+
messages: anthropicMessages,
|
|
109
|
+
...(anthropicTools.length > 0 && { tools: anthropicTools }),
|
|
110
|
+
stream: true,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Add system prompt with cache control if enabled
|
|
114
|
+
if (options.systemPrompt) {
|
|
115
|
+
if (this.enableCache && options.thinking !== 'disabled') {
|
|
116
|
+
params.system = [
|
|
117
|
+
{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: options.systemPrompt,
|
|
120
|
+
cache_control: { type: 'ephemeral' }
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
} else {
|
|
124
|
+
params.system = options.systemPrompt;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add beta header for caching
|
|
129
|
+
const requestOptions: any = {};
|
|
130
|
+
if (this.enableCache && options.thinking !== 'disabled') {
|
|
131
|
+
requestOptions.headers = {
|
|
132
|
+
'anthropic-beta': 'prompt-caching-2024-07-31'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const stream = await this.client.messages.create(params, requestOptions) as unknown as AsyncIterable<Anthropic.Messages.MessageStreamEvent>;
|
|
137
|
+
|
|
138
|
+
for await (const event of stream) {
|
|
139
|
+
if (event.type === 'message_start') {
|
|
140
|
+
yield { type: 'message_start', model: event.message.model };
|
|
141
|
+
} else if (event.type === 'content_block_start') {
|
|
142
|
+
if (event.content_block.type === 'text') {
|
|
143
|
+
// noop, deltas follow
|
|
144
|
+
} else if (event.content_block.type === 'tool_use') {
|
|
145
|
+
yield {
|
|
146
|
+
type: 'tool_call_start',
|
|
147
|
+
toolCallId: event.content_block.id,
|
|
148
|
+
toolName: event.content_block.name,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
} else if (event.type === 'content_block_delta') {
|
|
152
|
+
if (event.delta.type === 'text_delta') {
|
|
153
|
+
yield { type: 'text_delta', delta: event.delta.text };
|
|
154
|
+
} else if ((event.delta as any).type === 'thinking_delta') {
|
|
155
|
+
yield { type: 'thinking_delta', delta: (event.delta as any).thinking };
|
|
156
|
+
} else if (event.delta.type === 'input_json_delta') {
|
|
157
|
+
yield {
|
|
158
|
+
type: 'tool_call_delta',
|
|
159
|
+
toolCallId: (event as Anthropic.ContentBlockDeltaEvent).index?.toString() || '',
|
|
160
|
+
delta: event.delta.partial_json,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
} else if (event.type === 'content_block_stop') {
|
|
164
|
+
// noop
|
|
165
|
+
} else if (event.type === 'message_delta') {
|
|
166
|
+
if (event.delta.stop_reason) {
|
|
167
|
+
const usage: TokenUsage = {
|
|
168
|
+
inputTokens: (event.usage as any)?.input_tokens ?? 0,
|
|
169
|
+
outputTokens: (event.usage as any)?.output_tokens ?? 0,
|
|
170
|
+
};
|
|
171
|
+
yield {
|
|
172
|
+
type: 'message_complete',
|
|
173
|
+
stopReason: this.convertStopReason(event.delta.stop_reason),
|
|
174
|
+
usage,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
} else if (event.type === 'message_stop') {
|
|
178
|
+
// Stream complete
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
if (err instanceof Anthropic.APIError) {
|
|
183
|
+
yield { type: 'error', error: new ModelError(err.message, err.status, 'anthropic', err) };
|
|
184
|
+
} else {
|
|
185
|
+
yield { type: 'error', error: err };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async countTokens(messages: Message[]): Promise<number> {
|
|
191
|
+
// Approximate: ~4 chars per token
|
|
192
|
+
const text = messages.map(m => {
|
|
193
|
+
if (typeof m.content === 'string') return m.content;
|
|
194
|
+
return m.content.map(c => c.type === 'text' ? (c as any).text || '' : '').join('');
|
|
195
|
+
}).join('');
|
|
196
|
+
return Math.ceil(text.length / 4);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// --- Conversion helpers ---
|
|
200
|
+
|
|
201
|
+
private convertMessages(messages: Message[]): Anthropic.MessageParam[] {
|
|
202
|
+
const result: Anthropic.MessageParam[] = [];
|
|
203
|
+
|
|
204
|
+
for (const msg of messages) {
|
|
205
|
+
if (msg.role === 'system') continue; // System messages handled separately
|
|
206
|
+
|
|
207
|
+
if (msg.role === 'tool') {
|
|
208
|
+
const toolResults = msg.content
|
|
209
|
+
.filter((c): c is ToolResultContent => c.type === 'tool_result')
|
|
210
|
+
.map((c): Anthropic.ToolResultBlockParam => ({
|
|
211
|
+
type: 'tool_result',
|
|
212
|
+
tool_use_id: c.tool_use_id,
|
|
213
|
+
content: typeof c.content === 'string' ? c.content : c.content.map((cc) => 'text' in cc ? (cc as any).text || '' : '').join(''),
|
|
214
|
+
is_error: c.is_error,
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
result.push({ role: 'user', content: toolResults });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const content = msg.content
|
|
222
|
+
.map((c) => {
|
|
223
|
+
if (c.type === 'text') return { type: 'text' as const, text: c.text };
|
|
224
|
+
if (c.type === 'tool_use') return { type: 'tool_use' as const, id: c.id, name: c.name, input: c.input };
|
|
225
|
+
if (c.type === 'image') return {
|
|
226
|
+
type: 'image' as const,
|
|
227
|
+
source: { type: c.source.type === 'url' ? 'url' : 'base64', media_type: c.source.media_type, data: c.source.data || c.source.url || '' },
|
|
228
|
+
};
|
|
229
|
+
return null;
|
|
230
|
+
})
|
|
231
|
+
.filter((c): c is NonNullable<typeof c> => c !== null) as Anthropic.ContentBlock[];
|
|
232
|
+
|
|
233
|
+
result.push({ role: msg.role as 'user' | 'assistant', content });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private convertTools(tools: ToolDefinition[]): Anthropic.Tool[] {
|
|
240
|
+
return tools.map((t) => ({
|
|
241
|
+
name: t.name,
|
|
242
|
+
description: t.description,
|
|
243
|
+
input_schema: t.inputSchema as Anthropic.Tool.InputSchema,
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private convertResponseContent(content: Anthropic.Message['content']): ContentBlock[] {
|
|
248
|
+
return content.map((block) => {
|
|
249
|
+
if (block.type === 'text') return { type: 'text' as const, text: block.text };
|
|
250
|
+
if (block.type === 'tool_use') return {
|
|
251
|
+
type: 'tool_use' as const,
|
|
252
|
+
id: createToolCallId(block.id),
|
|
253
|
+
name: block.name,
|
|
254
|
+
input: block.input as Record<string, unknown>,
|
|
255
|
+
};
|
|
256
|
+
return { type: 'text', text: JSON.stringify(block) };
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private convertUsage(usage: Anthropic.Usage): TokenUsage {
|
|
261
|
+
const usageAny = usage as any;
|
|
262
|
+
return {
|
|
263
|
+
inputTokens: usage.input_tokens,
|
|
264
|
+
outputTokens: usage.output_tokens,
|
|
265
|
+
cacheReadTokens: usageAny.cache_read_input_tokens as number | undefined,
|
|
266
|
+
cacheWriteTokens: usageAny.cache_creation_input_tokens as number | undefined,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private convertStopReason(reason: string | null): ModelResponse['stopReason'] {
|
|
271
|
+
switch (reason) {
|
|
272
|
+
case 'end_turn': return 'end_turn';
|
|
273
|
+
case 'tool_use': return 'tool_use';
|
|
274
|
+
case 'max_tokens': return 'max_tokens';
|
|
275
|
+
case 'stop_sequence': return 'stop_sequence';
|
|
276
|
+
default: return 'end_turn';
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodingPlanProvider.d.ts","sourceRoot":"","sources":["CodingPlanProvider.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,wBAAwB,EAAE,KAAK,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEtG,uCAAuC;AACvC,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,SAAS,GACT,YAAY,GACZ,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,QAAQ,CAAC;AAEb,gCAAgC;AAChC,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC;IAC/E,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAoDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,kBAAmB,SAAQ,wBAAwB;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,CAAC;gBAEvB,MAAM,EAAE,gBAAgB;IAmCpC;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,eAAe,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IASlF;;OAEG;IACH,mBAAmB,IAAI,MAAM,GAAG,IAAI;CAIrC;AAED;;GAEG;AACH,wBAAgB,+BAA+B,IAAI,KAAK,CAAC;IACvD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CASD;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,kBAAkB,EAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,MAAM,GACrB,kBAAkB,CAOpB"}
|