godcrm/backend/routes/v3/ai-agents/providersModelsController.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

251 lines
11 KiB
JavaScript

/**
* Providers & Models Controller
* GET/PUT /providers, GET /providers/:providerId/models
* POST /providers/:providerId/refresh-models, GET /models
*/
import { Router } from 'express';
import { dbGet, dbRun, dbAll, isPostgres } from '../../../database/connection.js';
import { apiLogger } from '../../../utils/logger.js';
import { success, notFound, badRequest, error } from '../../../utils/response.js';
import { safeParseJSON } from './shared.js';
const router = Router();
/**
* POST /api/v3/ai/providers/:providerId/refresh-models
*/
router.post('/providers/:providerId/refresh-models', async (req, res) => {
try {
const { providerId } = req.params;
const userId = req.user?.id;
const providerRow = await dbGet(`
SELECT tr.id, tr.data, tr.table_id FROM table_rows tr
JOIN universal_tables ut ON tr.table_id = ut.id
WHERE tr.id = ? AND ut.name LIKE '%Providers%'
`, [providerId]);
if (!providerRow) return notFound(res, 'Provider not found');
const provider = safeParseJSON(providerRow.data, {});
let apiKey = null;
if (provider.api_key_id) {
const keyRow = await dbGet(`SELECT data FROM table_rows WHERE id = ?`, [provider.api_key_id]);
if (keyRow) { apiKey = safeParseJSON(keyRow.data, {}).api_key; }
}
if (!apiKey) return badRequest(res, 'No API key configured for this provider');
const modelsTable = await dbGet(`
SELECT ut.id FROM universal_tables ut
JOIN projects p ON ut.project_id = p.id
WHERE ut.name LIKE '%Models%' AND p.space_id = 10 LIMIT 1
`);
if (!modelsTable) return notFound(res, 'Models table not found');
let models = [];
if (provider.api_id === 'openai') {
const response = await fetch(`${provider.base_url}/models`, { headers: { 'Authorization': `Bearer ${apiKey}` } });
if (!response.ok) {
const err = await response.text();
return error(res, 'OPENAI_API_ERROR', `OpenAI API error: ${err}`, 500);
}
const data = await response.json();
models = data.data
.filter(m => m.id.includes('gpt') || m.id.includes('o1') || m.id.includes('chatgpt'))
.map(m => ({
model_id: m.id, name: m.id.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
provider_id: providerId, context_window: m.id.includes('gpt-4') ? 128000 : 16385,
is_active: true, deprecated: false
}));
} else if (provider.api_id === 'anthropic') {
models = [
{ model_id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', provider_id: providerId, context_window: 200000, is_active: true },
{ model_id: 'claude-3-opus-20240229', name: 'Claude 3 Opus', provider_id: providerId, context_window: 200000, is_active: true },
{ model_id: 'claude-3-sonnet-20240229', name: 'Claude 3 Sonnet', provider_id: providerId, context_window: 200000, is_active: true },
{ model_id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', provider_id: providerId, context_window: 200000, is_active: true }
];
} else if (provider.api_id === 'ollama') {
const response = await fetch(`${provider.base_url}/api/tags`);
if (!response.ok) return error(res, 'OLLAMA_ERROR', 'Failed to connect to Ollama', 500);
const data = await response.json();
models = (data.models || []).map(m => ({
model_id: m.name, name: m.name, provider_id: providerId, context_window: 8192, is_active: true
}));
}
await dbRun(`DELETE FROM table_rows WHERE table_id = ? AND json_extract(data, '$.provider_id') = ?`, [modelsTable.id, providerId]);
let addedCount = 0;
for (const model of models) {
const baseId = `model-${provider.api_id}-${model.model_id.replace(/[^a-z0-9]/gi, '-')}`;
await dbRun(`INSERT INTO table_rows (table_id, base_id, data, created_by, created_at, updated_at) VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))`,
[modelsTable.id, baseId, JSON.stringify(model), userId]);
addedCount++;
}
await dbRun(`UPDATE table_rows SET data = json_set(json_set(data, '$.models_count', ?), '$.last_refresh', ?) WHERE id = ?`,
[addedCount, new Date().toISOString(), providerId]);
return success(res, { data: { providerId, providerName: provider.name, modelsFound: models.length, modelsAdded: addedCount } });
} catch (err) {
apiLogger.error({ err }, 'Error refreshing models');
return error(res, 'REFRESH_MODELS_ERROR', 'Failed to refresh models: ' + err.message, 500);
}
});
/**
* GET /api/v3/ai/providers
*/
router.get('/providers', async (req, res) => {
try {
const spaceId = req.query.spaceId || req.headers['x-space-id'];
apiLogger.debug({ context: 'AI Providers', spaceId }, 'Request - spaceId');
let table;
if (spaceId) {
table = await dbGet(`
SELECT ut.id FROM universal_tables ut
JOIN projects p ON ut.project_id = p.id
WHERE (ut.name = 'AI Operators' OR ut.name = 'ai_operators') AND p.space_id = ?
LIMIT 1
`, [spaceId]);
}
if (!table) {
table = await dbGet(`SELECT ut.id FROM universal_tables ut WHERE ut.name = 'AI Operators' OR ut.name = 'ai_operators' ORDER BY id ASC LIMIT 1`);
}
if (!table) return success(res, { providers: [] });
const rows = await dbAll(`SELECT id, data FROM table_rows WHERE table_id = ?`, [table.id]);
const providers = rows.map(r => ({ id: r.id, ...safeParseJSON(r.data, {}) })).filter(p => p.id);
return success(res, { providers });
} catch (err) {
apiLogger.error({ err }, 'Error fetching providers');
return error(res, 'FETCH_PROVIDERS_ERROR', 'Failed to fetch providers', 500);
}
});
/**
* PUT /api/v3/ai/providers/:providerId
*/
router.put('/providers/:providerId', async (req, res) => {
try {
const { providerId } = req.params;
const { api_key } = req.body;
if (!api_key) return badRequest(res, 'api_key is required');
const row = await dbGet(`SELECT id, data FROM table_rows WHERE id = ?`, [providerId]);
if (!row) return notFound(res, 'Provider not found');
const data = safeParseJSON(row.data, {});
data.api_key = api_key;
if (!data.status) data.status = 'active';
await dbRun(`UPDATE table_rows SET data = ?, updated_at = datetime('now') WHERE id = ?`, [JSON.stringify(data), providerId]);
return success(res, null, 'Provider updated');
} catch (err) {
apiLogger.error({ err }, 'Error updating provider');
return error(res, 'UPDATE_PROVIDER_ERROR', 'Failed to update provider', 500);
}
});
/**
* GET /api/v3/ai/providers/:providerId/models
*/
router.get('/providers/:providerId/models', async (req, res) => {
try {
const { providerId } = req.params;
const spaceId = req.query.spaceId || req.headers['x-space-id'];
let table;
if (spaceId) {
table = await dbGet(`SELECT ut.id FROM universal_tables ut JOIN projects p ON ut.project_id = p.id WHERE (ut.name = 'AI Models' OR ut.name = 'ai_models') AND p.space_id = ? LIMIT 1`, [spaceId]);
}
if (!table) {
table = await dbGet(`SELECT ut.id FROM universal_tables ut WHERE ut.name = 'AI Models' OR ut.name = 'ai_models' LIMIT 1`);
}
if (!table) return success(res, { models: [] });
const providerIds = [String(providerId)];
const operatorRow = await dbGet('SELECT data FROM table_rows WHERE id = ?', [providerId]);
if (operatorRow) {
const opData = safeParseJSON(operatorRow.data, {});
if (opData.provider === 'claude-code' || opData.provider === 'anthropic') {
const anthropicOp = await dbGet(isPostgres()
? `SELECT tr.id FROM table_rows tr JOIN universal_tables ut ON tr.table_id = ut.id JOIN projects p ON ut.project_id = p.id
WHERE (ut.name LIKE '%Operators%') AND tr.data->>'provider' = 'anthropic' AND tr.data->>'name' = 'Anthropic'
AND p.space_id = (SELECT p2.space_id FROM universal_tables ut2 JOIN projects p2 ON ut2.project_id = p2.id WHERE ut2.id = $1) LIMIT 1`
: `SELECT tr.id FROM table_rows tr JOIN universal_tables ut ON tr.table_id = ut.id JOIN projects p ON ut.project_id = p.id
WHERE (ut.name LIKE '%Operators%') AND json_extract(tr.data, '$.provider') = 'anthropic' AND json_extract(tr.data, '$.name') = 'Anthropic'
AND p.space_id = (SELECT p2.space_id FROM universal_tables ut2 JOIN projects p2 ON ut2.project_id = p2.id WHERE ut2.id = ?) LIMIT 1`,
[table.id]);
if (anthropicOp && String(anthropicOp.id) !== String(providerId)) {
providerIds.push(String(anthropicOp.id));
}
}
}
const rows = await dbAll(isPostgres()
? `SELECT id, data FROM table_rows WHERE table_id = $1 AND (data->>'operator_id' = ANY($2) OR data->>'provider_id' = ANY($2)) ORDER BY data->>'name'`
: `SELECT id, data FROM table_rows WHERE table_id = ?
AND (${providerIds.map(() => "json_extract(data, '$.operator_id') = ?").join(' OR ')}
OR ${providerIds.map(() => "json_extract(data, '$.provider_id') = ?").join(' OR ')})
ORDER BY json_extract(data, '$.name')`,
isPostgres() ? [table.id, providerIds] : [table.id, ...providerIds, ...providerIds]
);
const models = rows.map(r => {
const data = safeParseJSON(r.data, {});
return { id: r.id, name: data.name || data.model_id, model_id: data.model_id, operator_id: data.operator_id, provider_id: data.provider_id || data.operator_id };
});
return success(res, { models });
} catch (err) {
apiLogger.error({ err }, 'Error fetching provider models');
return error(res, 'FETCH_MODELS_ERROR', 'Failed to fetch models', 500);
}
});
/**
* GET /api/v3/ai/models
*/
router.get('/models', async (req, res) => {
try {
const { providerId } = req.query;
const spaceId = req.query.spaceId || req.headers['x-space-id'];
let table;
if (spaceId) {
table = await dbGet(`SELECT ut.id FROM universal_tables ut JOIN projects p ON ut.project_id = p.id WHERE (ut.name = 'AI Models' OR ut.name = 'ai_models') AND p.space_id = ? LIMIT 1`, [spaceId]);
}
if (!table) {
table = await dbGet(`SELECT ut.id FROM universal_tables ut WHERE ut.name = 'AI Models' OR ut.name = 'ai_models' LIMIT 1`);
}
if (!table) return success(res, { models: [] });
let query = `SELECT id, data FROM table_rows WHERE table_id = ?`;
const params = [table.id];
if (providerId) {
query += ` AND (json_extract(data, '$.provider_id') = ? OR json_extract(data, '$.operator_id') = ?)`;
params.push(providerId, providerId);
}
query += ` ORDER BY json_extract(data, '$.name')`;
const rows = await dbAll(query, params);
const models = rows.map(r => ({ id: r.id, ...safeParseJSON(r.data, {}) })).filter(m => m.id);
return success(res, { models });
} catch (err) {
apiLogger.error({ err }, 'Error fetching models');
return error(res, 'FETCH_MODELS_ERROR', 'Failed to fetch models', 500);
}
});
export default router;