godcrm/backend/routes/v3/labs/ai.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

348 lines
11 KiB
JavaScript

/**
* Labs AI Integration Routes
* Handles AI agents, providers, templates endpoints
* Both v4 (direct DB) and legacy (universal tables) versions
*/
import { Router } from 'express';
import { dbGet, dbRun, dbAll } from '../../../database/connection.js';
import { apiLogger } from '../../../utils/logger.js';
import { success, created, notFound, badRequest } from '../../../utils/response.js';
import { generateId } from './_helpers.js';
import { getAgentTableIds } from '../../../services/agentTableResolver.js';
const router = Router();
// === V4 AI INTEGRATION ENDPOINTS ===
/**
* GET /api/v3/labs/ai/agents
* Get all AI agents from GOD CRM AI Pack
*/
router.get('/ai/agents', async (req, res) => {
try {
const agents = await dbAll(`
SELECT a.*, o.name as operator_name, o.integration_key, o.default_model
FROM ai_agents a
LEFT JOIN ai_operators o ON a.operator_id = o.id
ORDER BY a.name
`);
success(res, agents);
} catch (err) {
apiLogger.error({ err }, 'Failed to get AI agents');
badRequest(res, 'Failed to get AI agents');
}
});
/**
* GET /api/v3/labs/ai/providers
* Get all AI providers/operators from GOD CRM AI Pack
*/
router.get('/ai/providers', async (req, res) => {
try {
const providers = await dbAll(`
SELECT id, name, description, integration_key, default_model,
supported_models, created_at
FROM ai_operators
ORDER BY name
`);
success(res, providers);
} catch (err) {
apiLogger.error({ err }, 'Failed to get AI providers');
badRequest(res, 'Failed to get AI providers');
}
});
/**
* GET /api/v3/labs/ai/templates
* Get AI templates (placeholder for MindWorkflow integration)
*/
router.get('/ai/templates', async (req, res) => {
try {
// For now, return empty array - will be populated when MindWorkflow sync is implemented
const templates = [];
success(res, templates);
} catch (err) {
apiLogger.error({ err }, 'Failed to get AI templates');
badRequest(res, 'Failed to get AI templates');
}
});
/**
* POST /api/v3/labs/ai/templates/sync
* Sync templates from MindWorkflow (placeholder)
*/
router.post('/ai/templates/sync', async (req, res) => {
try {
// Placeholder for MindWorkflow template sync
apiLogger.info('AI templates sync requested (not yet implemented)');
success(res, {
synced: 0,
message: 'Template sync not yet implemented'
});
} catch (err) {
apiLogger.error({ err }, 'Failed to sync AI templates');
badRequest(res, 'Failed to sync AI templates');
}
});
/**
* POST /api/v3/labs/ai/templates/:templateId/create-agent
* Create agent from template (placeholder)
*/
router.post('/ai/templates/:templateId/create-agent', async (req, res) => {
try {
const { templateId } = req.params;
const { operator_id, custom_name } = req.body;
// Placeholder for agent creation from template
apiLogger.info({ templateId, operator_id, custom_name }, 'Agent creation from template requested (not yet implemented)');
// Return mock agent for now
const mockAgent = {
id: Date.now(),
name: custom_name || `Agent from Template ${templateId}`,
description: 'Created from MindWorkflow template',
operator_id,
created_at: new Date().toISOString()
};
created(res, mockAgent);
} catch (err) {
apiLogger.error({ err }, 'Failed to create agent from template');
badRequest(res, 'Failed to create agent from template');
}
});
// === LEGACY AI ENDPOINTS (universal tables system) ===
// GET /ai/agents (legacy - via universal tables)
// NOTE: This is registered after the v4 version above.
// Express uses the first matching route, so v4 version takes precedence.
// These legacy routes are kept for backward compatibility but won't be reached
// unless the v4 routes are removed. They are included here to preserve the
// exact same route registration order as the original file.
// Legacy: GET /ai/agents (via universal tables)
router.get('/ai/agents', async (req, res) => {
try {
// ADR-164 Phase 3a: resolve the AI Agents table via the marker-aware helper
// (earliest table id preserves the old `ORDER BY ut.id ASC LIMIT 1`).
const agentTableIds = await getAgentTableIds();
const table = agentTableIds.length ? { id: Math.min(...agentTableIds) } : null;
if (!table) {
return success(res, []);
}
// Get agents from table_rows
const agents = await dbAll(`
SELECT tr.id, tr.data
FROM table_rows tr
WHERE tr.table_id = ?
ORDER BY tr.id
`, [table.id]);
// Parse and format agent data
const formattedAgents = agents.map(row => {
const data = typeof row.data === 'string' ? JSON.parse(row.data) : row.data;
return {
id: row.id,
name: data.name || 'Unnamed Agent',
description: data.description || '',
system_prompt: data.system_prompt || '',
operator_id: data.operator_id || null
};
});
success(res, formattedAgents);
} catch (err) {
apiLogger.error({ err }, 'Failed to get AI agents');
badRequest(res, 'Failed to get AI agents');
}
});
// Legacy: GET /ai/providers (via universal tables)
router.get('/ai/providers', async (req, res) => {
try {
// Find AI Operators table through universal tables system
const table = await dbGet(`
SELECT ut.id
FROM universal_tables ut
WHERE ut.name = 'AI Operators' OR ut.name = 'ai_operators'
ORDER BY ut.id ASC
LIMIT 1
`);
if (!table) {
return success(res, []);
}
// Get providers from table_rows
const providers = await dbAll(`
SELECT tr.id, tr.data
FROM table_rows tr
WHERE tr.table_id = ?
ORDER BY tr.id
`, [table.id]);
// Parse and format provider data
const formattedProviders = providers.map(row => {
const data = typeof row.data === 'string' ? JSON.parse(row.data) : row.data;
return {
id: row.id,
name: data.name || 'Unnamed Provider',
description: data.description || ''
};
});
success(res, formattedProviders);
} catch (err) {
apiLogger.error({ err }, 'Failed to get AI providers');
badRequest(res, 'Failed to get AI providers');
}
});
// Legacy: GET /ai/templates - List AI templates
router.get('/ai/templates', async (req, res) => {
try {
const templates = await dbAll('SELECT * FROM labs_ai_templates ORDER BY name');
success(res, templates);
} catch (err) {
apiLogger.error({ err }, 'Failed to get AI templates');
badRequest(res, 'Failed to get AI templates');
}
});
// Legacy: POST /ai/templates/:id/create-agent - Create agent from template
router.post('/ai/templates/:id/create-agent', async (req, res) => {
try {
const { id } = req.params;
const { operator_id, custom_name } = req.body;
const template = await dbGet('SELECT * FROM labs_ai_templates WHERE id = $1', [id]);
if (!template) {
return notFound(res, 'Template');
}
// ADR-164 Phase 3a: resolve the AI Agents table via the marker-aware helper
// (earliest table id preserves the old `ORDER BY ut.id ASC LIMIT 1`).
const agentTableIds = await getAgentTableIds();
const table = agentTableIds.length ? { id: Math.min(...agentTableIds) } : null;
if (!table) {
return badRequest(res, 'AI Agents table not found');
}
// Parse settings and routing_config
const settings = typeof template.settings === 'string'
? JSON.parse(template.settings)
: template.settings || {};
const routingConfig = typeof template.routing_config === 'string'
? JSON.parse(template.routing_config)
: template.routing_config || {};
// Create agent data
const agentData = {
name: custom_name || template.name,
description: template.description,
system_prompt: template.system_prompt,
user_prompt: template.user_prompt_example,
operator_id: operator_id || null,
settings: {
...settings,
routing_config: routingConfig,
source_template: template.mindworkflow_id
}
};
// Insert into table_rows
const result = await dbRun(`
INSERT INTO table_rows (table_id, base_id, data, created_at, updated_at)
VALUES ($1, $2, $3, datetime('now'), datetime('now'))
`, [
table.id,
generateId('agent'),
JSON.stringify(agentData)
]);
const agentRowId = result.lastInsertRowid || result.insertId;
// Update template reference
if (agentRowId) {
await dbRun('UPDATE labs_ai_templates SET ai_agent_id = $1 WHERE id = $2', [agentRowId, id]);
}
// Return the created agent
const agent = {
id: agentRowId,
...agentData
};
created(res, agent, 'Agent created from template');
} catch (err) {
apiLogger.error({ err }, 'Failed to create agent from template');
badRequest(res, 'Failed to create agent');
}
});
// Legacy: POST /ai/templates/sync - Sync MindWorkflow templates
router.post('/ai/templates/sync', async (req, res) => {
try {
const mindworkflowTemplates = [
{
mindworkflow_id: 'planner_llm',
name: 'Strategic Planner',
category: 'text_to_text',
description: 'Long-form reasoning model for planning multi-step creative pipelines',
system_prompt: 'You are a senior creative strategist. Respond with numbered steps, each containing a goal, reasoning, and deliverable.',
user_prompt_example: 'Draft a 5 step plan for launching a brand on social media.',
settings: JSON.stringify({ model: 'gpt-4o-mini', temperature: 0.4 }),
routing_config: JSON.stringify({ outputs: [{ id: 'text', type: 'text', enabled: true }] })
},
{
mindworkflow_id: 'tone_refiner',
name: 'Tone Refiner',
category: 'text_to_text',
description: 'Refines copy to match brand voice and tone guidelines',
system_prompt: 'You are an expert copywriter. Refine the given text to match the specified tone.',
settings: JSON.stringify({ model: 'gpt-4o-mini', temperature: 0.3 })
},
{
mindworkflow_id: 'code_assistant',
name: 'Code Assistant',
category: 'text_to_text',
description: 'Helps with code generation, review, and debugging',
system_prompt: 'You are an expert developer. Help with code tasks.',
settings: JSON.stringify({ model: 'gpt-4o-mini', temperature: 0.2 })
}
];
let synced = 0;
for (const tpl of mindworkflowTemplates) {
const existing = await dbGet(
'SELECT id FROM labs_ai_templates WHERE mindworkflow_id = $1',
[tpl.mindworkflow_id]
);
if (!existing) {
await dbRun(`
INSERT INTO labs_ai_templates
(mindworkflow_id, name, category, description, system_prompt, user_prompt_example, settings, routing_config, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
`, [
tpl.mindworkflow_id, tpl.name, tpl.category, tpl.description,
tpl.system_prompt, tpl.user_prompt_example, tpl.settings, tpl.routing_config || '{}'
]);
synced++;
}
}
const templates = await dbAll('SELECT * FROM labs_ai_templates ORDER BY name');
success(res, { synced, total: templates.length, templates });
} catch (err) {
apiLogger.error({ err }, 'Failed to sync templates');
badRequest(res, 'Failed to sync templates');
}
});
export default router;