supa-seed
Version:
A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support
328 lines • 11.8 kB
JavaScript
;
/**
* Ollama Local Model Client
* Phase 5, Checkpoint E1 - Local AI integration for asset generation
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ollamaClient = exports.OllamaClient = void 0;
const logger_1 = require("../core/utils/logger");
const http = __importStar(require("http"));
const https = __importStar(require("https"));
class OllamaClient {
constructor(config) {
this.isHealthy = false;
this.lastHealthCheck = new Date(0);
this.healthCheckInterval = 30000; // 30 seconds
this.config = {
baseUrl: 'http://localhost:11434',
model: 'llama3.1:latest',
timeout: 30000,
maxRetries: 3,
fallbackModels: ['llama3.1:8b', 'llama2:latest', 'mistral:latest'],
...config
};
logger_1.Logger.info(`🤖 Initializing Ollama client: ${this.config.baseUrl}`);
}
/**
* Check if Ollama is available and healthy
*/
async checkHealth() {
const now = new Date();
// Skip frequent health checks
if (this.isHealthy && (now.getTime() - this.lastHealthCheck.getTime()) < this.healthCheckInterval) {
return {
connected: true,
availableModels: [],
recommendedModel: this.config.model
};
}
try {
// Check if Ollama is running
const version = await this.getVersion();
// Get available models
const models = await this.listModels();
const availableModels = models.map(m => m.name);
// Find best available model
const recommendedModel = this.findBestModel(availableModels);
this.isHealthy = true;
this.lastHealthCheck = now;
logger_1.Logger.info(`🤖 Ollama connected: ${availableModels.length} models available`);
return {
connected: true,
version,
availableModels,
recommendedModel
};
}
catch (error) {
this.isHealthy = false;
logger_1.Logger.warn(`🤖 Ollama connection failed: ${error.message}`);
return {
connected: false,
availableModels: [],
error: error.message
};
}
}
/**
* Generate text using Ollama
*/
async generate(request) {
const model = request.model || this.config.model;
// Health check before generation
const health = await this.checkHealth();
if (!health.connected) {
throw new Error(`Ollama not available: ${health.error}`);
}
// Ensure model is available
if (!health.availableModels.includes(model)) {
const fallback = this.findBestModel(health.availableModels);
if (!fallback) {
throw new Error(`No suitable models available. Requested: ${model}`);
}
logger_1.Logger.warn(`🤖 Model ${model} not available, using fallback: ${fallback}`);
request.model = fallback;
}
const fullRequest = {
model,
...request
};
logger_1.Logger.debug(`🤖 Generating with ${model}: ${request.prompt.substring(0, 100)}...`);
let attempt = 0;
while (attempt < this.config.maxRetries) {
try {
const response = await this.makeRequest('/api/generate', fullRequest);
logger_1.Logger.debug(`🤖 Generated ${response.response.length} characters in ${response.total_duration}ns`);
return response;
}
catch (error) {
attempt++;
if (attempt >= this.config.maxRetries) {
throw new Error(`Generation failed after ${attempt} attempts: ${error.message}`);
}
logger_1.Logger.warn(`🤖 Generation attempt ${attempt} failed, retrying: ${error.message}`);
await this.delay(1000 * attempt); // Exponential backoff
}
}
throw new Error('Generation failed: Maximum retries exceeded');
}
/**
* Generate structured JSON response
*/
async generateJSON(prompt, system, schema) {
try {
const response = await this.generate({
prompt,
system,
format: 'json',
options: {
temperature: 0.1, // Lower temperature for more consistent JSON
}
});
const parsed = JSON.parse(response.response);
// Basic schema validation if provided
if (schema && !this.validateSchema(parsed, schema)) {
throw new Error('Generated JSON does not match expected schema');
}
return parsed;
}
catch (error) {
if (error.message.includes('JSON')) {
logger_1.Logger.error(`🤖 JSON parsing failed: ${error.message}`);
throw new Error(`Invalid JSON response from model: ${error.message}`);
}
throw error;
}
}
/**
* Get available models
*/
async listModels() {
try {
const response = await this.makeRequest('/api/tags', null, 'GET');
return response.models || [];
}
catch (error) {
logger_1.Logger.error(`🤖 Failed to list models: ${error.message}`);
return [];
}
}
/**
* Pull a model if not available
*/
async pullModel(name, onProgress) {
try {
logger_1.Logger.info(`🤖 Pulling model: ${name}`);
// This would implement streaming for progress updates
// For now, simplified version
await this.makeRequest('/api/pull', { name });
logger_1.Logger.info(`🤖 Model pulled successfully: ${name}`);
return true;
}
catch (error) {
logger_1.Logger.error(`🤖 Failed to pull model ${name}: ${error.message}`);
return false;
}
}
/**
* Get Ollama version
*/
async getVersion() {
try {
const response = await this.makeRequest('/api/version', null, 'GET');
return response.version || 'unknown';
}
catch (error) {
throw new Error(`Failed to get Ollama version: ${error.message}`);
}
}
/**
* Find the best available model from our preferences
*/
findBestModel(availableModels) {
// Check configured model first
if (availableModels.includes(this.config.model)) {
return this.config.model;
}
// Check fallback models
if (this.config.fallbackModels) {
for (const fallback of this.config.fallbackModels) {
if (availableModels.includes(fallback)) {
return fallback;
}
}
}
// Return first available model
return availableModels[0];
}
/**
* Make HTTP request to Ollama API
*/
async makeRequest(endpoint, data, method = 'POST') {
return new Promise((resolve, reject) => {
const url = new URL(endpoint, this.config.baseUrl);
const isHttps = url.protocol === 'https:';
const httpModule = isHttps ? https : http;
const postData = data ? JSON.stringify(data) : undefined;
const options = {
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: url.pathname + url.search,
method,
headers: {
'Content-Type': 'application/json',
...(postData && { 'Content-Length': Buffer.byteLength(postData) })
},
timeout: this.config.timeout
};
const req = httpModule.request(options, (res) => {
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
try {
if (res.statusCode >= 200 && res.statusCode < 300) {
const response = body ? JSON.parse(body) : {};
resolve(response);
}
else {
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
}
}
catch (error) {
reject(new Error(`Failed to parse response: ${error.message}`));
}
});
});
req.on('error', (error) => {
reject(new Error(`Request failed: ${error.message}`));
});
req.on('timeout', () => {
req.destroy();
reject(new Error(`Request timeout after ${this.config.timeout}ms`));
});
if (postData) {
req.write(postData);
}
req.end();
});
}
/**
* Basic schema validation
*/
validateSchema(data, schema) {
// Very basic validation - in production would use proper JSON schema validator
if (typeof schema === 'object' && schema !== null) {
for (const key in schema) {
if (!(key in data)) {
return false;
}
}
}
return true;
}
/**
* Simple delay utility
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get connection status
*/
isConnected() {
return this.isHealthy;
}
/**
* Get current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Update configuration
*/
updateConfig(updates) {
this.config = { ...this.config, ...updates };
this.isHealthy = false; // Force health check on next request
logger_1.Logger.info('🤖 Ollama configuration updated');
}
}
exports.OllamaClient = OllamaClient;
// Export singleton instance
exports.ollamaClient = new OllamaClient();
//# sourceMappingURL=ollama-client.js.map