lanonasis-memory
Version:
Memory as a Service integration - AI-powered memory management with semantic search (Compatible with CLI v3.0.6+)
245 lines (202 loc) • 8.26 kB
text/typescript
import * as vscode from 'vscode';
import { SecureApiKeyService, StoredCredential } from './SecureApiKeyService';
export interface ApiKey {
id: string;
name: string;
keyType: string;
environment: string;
accessLevel: string;
projectId: string;
createdAt: string;
expiresAt?: string;
tags: string[];
metadata: Record<string, any>;
}
export interface Project {
id: string;
name: string;
description?: string;
organizationId: string;
createdAt: string;
teamMembers: string[];
settings: Record<string, any>;
}
export interface CreateApiKeyRequest {
name: string;
value: string;
keyType: 'api_key' | 'database_url' | 'oauth_token' | 'certificate' | 'ssh_key' | 'webhook_secret' | 'encryption_key';
environment: 'development' | 'staging' | 'production';
accessLevel: 'public' | 'authenticated' | 'team' | 'admin' | 'enterprise';
projectId: string;
tags?: string[];
expiresAt?: string;
rotationFrequency?: number;
metadata?: Record<string, any>;
}
export interface CreateProjectRequest {
name: string;
description?: string;
organizationId: string;
teamMembers?: string[];
settings?: Record<string, any>;
}
export class ApiKeyService {
private config: vscode.WorkspaceConfiguration;
private baseUrl: string = 'https://mcp.lanonasis.com';
private secureApiKeyService: SecureApiKeyService;
constructor(secureApiKeyService: SecureApiKeyService) {
this.secureApiKeyService = secureApiKeyService;
this.config = vscode.workspace.getConfiguration('lanonasis');
this.updateConfig();
}
private updateConfig(): void {
const useGateway = this.config.get<boolean>('useGateway', true);
const apiUrl = this.config.get<string>('apiUrl', 'https://mcp.lanonasis.com');
const gatewayUrl = this.config.get<string>('gatewayUrl', 'https://mcp.lanonasis.com');
this.baseUrl = this.sanitizeBaseUrl(useGateway ? gatewayUrl : apiUrl);
}
refreshConfig(): void {
this.config = vscode.workspace.getConfiguration('lanonasis');
this.updateConfig();
}
private async makeRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const credentials = await this.resolveCredentials();
const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
const url = `${this.baseUrl}${normalizedEndpoint}`;
const authHeaders: Record<string, string> = credentials.type === 'oauth'
? { 'Authorization': `Bearer ${credentials.token}` }
: { 'X-API-Key': credentials.token };
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...authHeaders,
...options.headers
}
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
return response.json();
}
private sanitizeBaseUrl(url: string): string {
if (!url) {
return 'https://mcp.lanonasis.com';
}
let clean = url.trim();
// remove trailing slashes
clean = clean.replace(/\/+$/, '');
// remove duplicate /api or /api/v1 suffixes
clean = clean.replace(/\/api\/v1$/i, '').replace(/\/api$/i, '');
return clean || 'https://mcp.lanonasis.com';
}
private async resolveCredentials(): Promise<StoredCredential> {
let credentials = await this.secureApiKeyService.getStoredCredentials();
if (!credentials) {
const value = await this.secureApiKeyService.getApiKeyOrPrompt();
if (!value) {
throw new Error('API key not configured. Please configure your API key to use Lanonasis services.');
}
credentials = await this.secureApiKeyService.getStoredCredentials();
if (!credentials) {
credentials = {
type: this.looksLikeJwt(value) ? 'oauth' : 'apiKey',
token: value
};
}
}
return credentials;
}
private looksLikeJwt(token: string): boolean {
const parts = token.split('.');
if (parts.length !== 3) {
return false;
}
const jwtSegment = /^[A-Za-z0-9-_]+$/;
return parts.every(segment => jwtSegment.test(segment));
}
// ============================================================================
// PROJECT MANAGEMENT
// ============================================================================
async getProjects(): Promise<Project[]> {
return this.makeRequest<Project[]>('/api/v1/projects');
}
async getProject(projectId: string): Promise<Project> {
return this.makeRequest<Project>(`/api/v1/projects/${projectId}`);
}
async createProject(request: CreateProjectRequest): Promise<Project> {
return this.makeRequest<Project>('/api/v1/projects', {
method: 'POST',
body: JSON.stringify(request)
});
}
async updateProject(projectId: string, updates: Partial<CreateProjectRequest>): Promise<Project> {
return this.makeRequest<Project>(`/api/v1/projects/${projectId}`, {
method: 'PUT',
body: JSON.stringify(updates)
});
}
async deleteProject(projectId: string): Promise<void> {
await this.makeRequest<void>(`/api/v1/projects/${projectId}`, {
method: 'DELETE'
});
}
// ============================================================================
// API KEY MANAGEMENT
// ============================================================================
async getApiKeys(projectId?: string): Promise<ApiKey[]> {
const endpoint = projectId ? `/api/v1/projects/${projectId}/api-keys` : '/api/v1/auth/api-keys';
const response = await this.makeRequest<any>(endpoint);
// Handle wrapped response format from /api/v1/auth/api-keys
// which returns { success: true, data: [...] }
if (response && typeof response === 'object' && 'data' in response && Array.isArray(response.data)) {
return response.data;
}
// Handle direct array response from /api/v1/projects/:projectId/api-keys
if (Array.isArray(response)) {
return response;
}
// Fallback: return empty array if response format is unexpected
return [];
}
async getApiKey(keyId: string): Promise<ApiKey> {
return this.makeRequest<ApiKey>(`/api/v1/auth/api-keys/${keyId}`);
}
async createApiKey(request: CreateApiKeyRequest): Promise<ApiKey> {
return this.makeRequest<ApiKey>('/api/v1/auth/api-keys', {
method: 'POST',
body: JSON.stringify(request)
});
}
async updateApiKey(keyId: string, updates: Partial<CreateApiKeyRequest>): Promise<ApiKey> {
return this.makeRequest<ApiKey>(`/api/v1/auth/api-keys/${keyId}`, {
method: 'PUT',
body: JSON.stringify(updates)
});
}
async deleteApiKey(keyId: string): Promise<void> {
await this.makeRequest<void>(`/api/v1/auth/api-keys/${keyId}`, {
method: 'DELETE'
});
}
async rotateApiKey(keyId: string): Promise<ApiKey> {
return this.makeRequest<ApiKey>(`/api/v1/auth/api-keys/${keyId}/rotate`, {
method: 'POST'
});
}
// ============================================================================
// UTILITY METHODS
// ============================================================================
async testConnection(): Promise<boolean> {
try {
await this.makeRequest<any>('/api/v1/health');
return true;
} catch (error) {
return false;
}
}
async getUserInfo(): Promise<any> {
return this.makeRequest<any>('/api/v1/auth/me');
}
}