express-genix 4.5.2 → 4.6.1
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.
- package/README.md +18 -0
- package/index.js +29 -112
- package/lib/cleanup.js +7 -5
- package/lib/features.js +97 -9
- package/lib/generator.js +3 -2
- package/lib/scaffold.js +165 -0
- package/package.json +1 -1
- package/templates/controllers/authController.js.ejs +16 -7
- package/templates/core/docker-compose.yml.ejs +16 -3
- package/templates/core/env.ejs +1 -3
- package/templates/core/env.example.ejs +1 -3
- package/templates/core/server.js.ejs +6 -21
- package/templates/infra/Makefile.ejs +30 -0
- package/templates/infra/nginx.conf.ejs +25 -0
- package/templates/middleware/errorHandler.js.ejs +7 -1
- package/templates/middleware/validation.js.ejs +2 -2
- package/templates/migrations/create-users.js.ejs +5 -1
- package/templates/scaffold/controller.js.ejs +94 -0
- package/templates/scaffold/route.js.ejs +88 -0
- package/templates/scaffold/service.js.ejs +77 -0
- package/templates/services/authService.js.ejs +20 -2
- package/templates/services/userService.prisma.js.ejs +3 -2
- package/lib/ai-cli.js +0 -133
- package/templates/agents/graph.js.ejs +0 -84
- package/templates/config/ai.js.ejs +0 -47
- package/templates/controllers/aiController.js.ejs +0 -100
- package/templates/routes/aiRoutes.js.ejs +0 -117
- package/templates/services/aiService.js.ejs +0 -80
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
const { createReactAgent } = require('@langchain/langgraph/prebuilt');
|
|
2
|
-
const { tool } = require('@langchain/core/tools');
|
|
3
|
-
const { HumanMessage } = require('@langchain/core/messages');
|
|
4
|
-
const { z } = require('zod');
|
|
5
|
-
const { getModel } = require('../config/ai');
|
|
6
|
-
|
|
7
|
-
// --- Built-in Tools ---
|
|
8
|
-
|
|
9
|
-
const currentTimeTool = tool(
|
|
10
|
-
async () => new Date().toISOString(),
|
|
11
|
-
{
|
|
12
|
-
name: 'current_time',
|
|
13
|
-
description: 'Get the current date and time in ISO 8601 format',
|
|
14
|
-
schema: z.object({}),
|
|
15
|
-
}
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const calculatorTool = tool(
|
|
19
|
-
async ({ expression }) => {
|
|
20
|
-
// Only allow digits and math operators — no code injection possible
|
|
21
|
-
const sanitized = expression.replace(/[^0-9+\-*/().%\s]/g, '');
|
|
22
|
-
if (sanitized !== expression.trim()) {
|
|
23
|
-
return 'Error: Expression contains invalid characters. Use only numbers and +, -, *, /, (, ), %';
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const result = new Function(`"use strict"; return (${sanitized})`)();
|
|
27
|
-
if (!isFinite(result)) return 'Error: Result is not a finite number';
|
|
28
|
-
return String(result);
|
|
29
|
-
} catch {
|
|
30
|
-
return 'Error: Could not evaluate expression';
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: 'calculator',
|
|
35
|
-
description: 'Evaluate a mathematical expression using standard operators: +, -, *, /, %',
|
|
36
|
-
schema: z.object({
|
|
37
|
-
expression: z.string().describe('Math expression, e.g. "2 + 3 * 4"'),
|
|
38
|
-
}),
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create a LangGraph ReAct agent with tool-calling capabilities.
|
|
44
|
-
*
|
|
45
|
-
* @param {Object} options
|
|
46
|
-
* @param {Array} options.tools - Additional @langchain/core tools to register
|
|
47
|
-
* @param {string} options.systemPrompt - System instruction for the agent
|
|
48
|
-
* @param {string} options.model - Model name override
|
|
49
|
-
*/
|
|
50
|
-
const createAgent = (options = {}) => {
|
|
51
|
-
const tools = [
|
|
52
|
-
currentTimeTool,
|
|
53
|
-
calculatorTool,
|
|
54
|
-
...(options.tools || []),
|
|
55
|
-
];
|
|
56
|
-
|
|
57
|
-
const model = getModel(options);
|
|
58
|
-
|
|
59
|
-
return createReactAgent({
|
|
60
|
-
llm: model,
|
|
61
|
-
tools,
|
|
62
|
-
messageModifier: options.systemPrompt || 'You are a helpful assistant. Use the provided tools when needed to answer questions accurately.',
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Run the agent with a single message and return the final response.
|
|
68
|
-
*/
|
|
69
|
-
const runAgent = async (message, options = {}) => {
|
|
70
|
-
const agent = createAgent(options);
|
|
71
|
-
|
|
72
|
-
const result = await agent.invoke({
|
|
73
|
-
messages: [new HumanMessage(message)],
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const lastMessage = result.messages[result.messages.length - 1];
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
content: lastMessage.content,
|
|
80
|
-
steps: result.messages.length,
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
module.exports = { createAgent, runAgent, currentTimeTool, calculatorTool };
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<% if (aiProvider === 'openai') { %>
|
|
2
|
-
const { ChatOpenAI } = require('@langchain/openai');
|
|
3
|
-
<% } else if (aiProvider === 'anthropic') { %>
|
|
4
|
-
const { ChatAnthropic } = require('@langchain/anthropic');
|
|
5
|
-
<% } else if (aiProvider === 'gemini') { %>
|
|
6
|
-
const { ChatGoogleGenerativeAI } = require('@langchain/google-genai');
|
|
7
|
-
<% } else if (aiProvider === 'ollama') { %>
|
|
8
|
-
const { ChatOllama } = require('@langchain/ollama');
|
|
9
|
-
<% } %>
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Create a LangChain chat model based on the configured provider.
|
|
13
|
-
*/
|
|
14
|
-
const getModel = (options = {}) => {
|
|
15
|
-
const temperature = options.temperature ?? parseFloat(process.env.AI_TEMPERATURE || '0.7');
|
|
16
|
-
const maxTokens = options.maxTokens ?? parseInt(process.env.AI_MAX_TOKENS || '2048', 10);
|
|
17
|
-
<% if (aiProvider === 'openai') { %>
|
|
18
|
-
return new ChatOpenAI({
|
|
19
|
-
modelName: options.model || process.env.AI_MODEL || 'gpt-4o-mini',
|
|
20
|
-
temperature,
|
|
21
|
-
maxTokens,
|
|
22
|
-
openAIApiKey: process.env.OPENAI_API_KEY,
|
|
23
|
-
});
|
|
24
|
-
<% } else if (aiProvider === 'anthropic') { %>
|
|
25
|
-
return new ChatAnthropic({
|
|
26
|
-
modelName: options.model || process.env.AI_MODEL || 'claude-sonnet-4-20250514',
|
|
27
|
-
temperature,
|
|
28
|
-
maxTokens,
|
|
29
|
-
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
30
|
-
});
|
|
31
|
-
<% } else if (aiProvider === 'gemini') { %>
|
|
32
|
-
return new ChatGoogleGenerativeAI({
|
|
33
|
-
model: options.model || process.env.AI_MODEL || 'gemini-2.0-flash',
|
|
34
|
-
temperature,
|
|
35
|
-
maxOutputTokens: maxTokens,
|
|
36
|
-
apiKey: process.env.GOOGLE_API_KEY,
|
|
37
|
-
});
|
|
38
|
-
<% } else if (aiProvider === 'ollama') { %>
|
|
39
|
-
return new ChatOllama({
|
|
40
|
-
model: options.model || process.env.AI_MODEL || 'llama3',
|
|
41
|
-
temperature,
|
|
42
|
-
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
|
43
|
-
});
|
|
44
|
-
<% } %>
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
module.exports = { getModel };
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
const aiService = require('../services/aiService');
|
|
2
|
-
const { runAgent } = require('../agents/graph');
|
|
3
|
-
const { success, error } = require('../utils/response');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* POST /ai/chat — Send a message, get a complete AI response.
|
|
7
|
-
*/
|
|
8
|
-
const chatHandler = async (req, res, next) => {
|
|
9
|
-
try {
|
|
10
|
-
const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
|
|
11
|
-
|
|
12
|
-
if (!message) {
|
|
13
|
-
return error(res, 'Message is required', 400);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const result = await aiService.chat(message, {
|
|
17
|
-
systemPrompt,
|
|
18
|
-
history,
|
|
19
|
-
model,
|
|
20
|
-
temperature,
|
|
21
|
-
maxTokens,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
return success(res, result);
|
|
25
|
-
} catch (err) {
|
|
26
|
-
next(err);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* POST /ai/stream — Stream an AI response via Server-Sent Events.
|
|
32
|
-
*/
|
|
33
|
-
const streamHandler = async (req, res, next) => {
|
|
34
|
-
try {
|
|
35
|
-
const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
|
|
36
|
-
|
|
37
|
-
if (!message) {
|
|
38
|
-
return error(res, 'Message is required', 400);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
42
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
43
|
-
res.setHeader('Connection', 'keep-alive');
|
|
44
|
-
|
|
45
|
-
const generator = aiService.stream(message, {
|
|
46
|
-
systemPrompt,
|
|
47
|
-
history,
|
|
48
|
-
model,
|
|
49
|
-
temperature,
|
|
50
|
-
maxTokens,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
for await (const chunk of generator) {
|
|
54
|
-
res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
res.write('data: [DONE]\n\n');
|
|
58
|
-
res.end();
|
|
59
|
-
} catch (err) {
|
|
60
|
-
next(err);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* POST /ai/chain — Run a prompt template chain with variables.
|
|
66
|
-
*/
|
|
67
|
-
const chainHandler = async (req, res, next) => {
|
|
68
|
-
try {
|
|
69
|
-
const { template, variables, model } = req.body;
|
|
70
|
-
|
|
71
|
-
if (!template) {
|
|
72
|
-
return error(res, 'Template is required', 400);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const result = await aiService.chain(template, variables || {}, { model });
|
|
76
|
-
return success(res, { content: result });
|
|
77
|
-
} catch (err) {
|
|
78
|
-
next(err);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* POST /ai/agent — Run the LangGraph ReAct agent.
|
|
84
|
-
*/
|
|
85
|
-
const agentHandler = async (req, res, next) => {
|
|
86
|
-
try {
|
|
87
|
-
const { message, systemPrompt, model } = req.body;
|
|
88
|
-
|
|
89
|
-
if (!message) {
|
|
90
|
-
return error(res, 'Message is required', 400);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const result = await runAgent(message, { systemPrompt, model });
|
|
94
|
-
return success(res, result);
|
|
95
|
-
} catch (err) {
|
|
96
|
-
next(err);
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
module.exports = { chatHandler, streamHandler, chainHandler, agentHandler };
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const { chatHandler, streamHandler, chainHandler, agentHandler } = require('../controllers/aiController');
|
|
3
|
-
|
|
4
|
-
const router = express.Router();
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @swagger
|
|
8
|
-
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/chat:
|
|
9
|
-
* post:
|
|
10
|
-
* summary: Send a chat message to the AI
|
|
11
|
-
* tags: [AI]
|
|
12
|
-
* requestBody:
|
|
13
|
-
* required: true
|
|
14
|
-
* content:
|
|
15
|
-
* application/json:
|
|
16
|
-
* schema:
|
|
17
|
-
* type: object
|
|
18
|
-
* required: [message]
|
|
19
|
-
* properties:
|
|
20
|
-
* message:
|
|
21
|
-
* type: string
|
|
22
|
-
* example: "What is Node.js?"
|
|
23
|
-
* systemPrompt:
|
|
24
|
-
* type: string
|
|
25
|
-
* example: "You are a helpful coding assistant."
|
|
26
|
-
* history:
|
|
27
|
-
* type: array
|
|
28
|
-
* items:
|
|
29
|
-
* type: object
|
|
30
|
-
* properties:
|
|
31
|
-
* role:
|
|
32
|
-
* type: string
|
|
33
|
-
* enum: [user, assistant]
|
|
34
|
-
* content:
|
|
35
|
-
* type: string
|
|
36
|
-
* model:
|
|
37
|
-
* type: string
|
|
38
|
-
* responses:
|
|
39
|
-
* 200:
|
|
40
|
-
* description: AI response
|
|
41
|
-
*/
|
|
42
|
-
router.post('/chat', chatHandler);
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @swagger
|
|
46
|
-
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/stream:
|
|
47
|
-
* post:
|
|
48
|
-
* summary: Stream an AI response via Server-Sent Events
|
|
49
|
-
* tags: [AI]
|
|
50
|
-
* requestBody:
|
|
51
|
-
* required: true
|
|
52
|
-
* content:
|
|
53
|
-
* application/json:
|
|
54
|
-
* schema:
|
|
55
|
-
* type: object
|
|
56
|
-
* required: [message]
|
|
57
|
-
* properties:
|
|
58
|
-
* message:
|
|
59
|
-
* type: string
|
|
60
|
-
* responses:
|
|
61
|
-
* 200:
|
|
62
|
-
* description: SSE stream of AI response chunks
|
|
63
|
-
*/
|
|
64
|
-
router.post('/stream', streamHandler);
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @swagger
|
|
68
|
-
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/chain:
|
|
69
|
-
* post:
|
|
70
|
-
* summary: Run a prompt template chain with variable substitution
|
|
71
|
-
* tags: [AI]
|
|
72
|
-
* requestBody:
|
|
73
|
-
* required: true
|
|
74
|
-
* content:
|
|
75
|
-
* application/json:
|
|
76
|
-
* schema:
|
|
77
|
-
* type: object
|
|
78
|
-
* required: [template]
|
|
79
|
-
* properties:
|
|
80
|
-
* template:
|
|
81
|
-
* type: string
|
|
82
|
-
* example: "Summarize the following text: {text}"
|
|
83
|
-
* variables:
|
|
84
|
-
* type: object
|
|
85
|
-
* example: { "text": "Node.js is a JavaScript runtime..." }
|
|
86
|
-
* responses:
|
|
87
|
-
* 200:
|
|
88
|
-
* description: Chain result
|
|
89
|
-
*/
|
|
90
|
-
router.post('/chain', chainHandler);
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @swagger
|
|
94
|
-
* <% if (hasApiVersioning) { %>/v1<% } %>/ai/agent:
|
|
95
|
-
* post:
|
|
96
|
-
* summary: Run the LangGraph ReAct agent with tool calling
|
|
97
|
-
* tags: [AI]
|
|
98
|
-
* requestBody:
|
|
99
|
-
* required: true
|
|
100
|
-
* content:
|
|
101
|
-
* application/json:
|
|
102
|
-
* schema:
|
|
103
|
-
* type: object
|
|
104
|
-
* required: [message]
|
|
105
|
-
* properties:
|
|
106
|
-
* message:
|
|
107
|
-
* type: string
|
|
108
|
-
* example: "What time is it right now?"
|
|
109
|
-
* systemPrompt:
|
|
110
|
-
* type: string
|
|
111
|
-
* responses:
|
|
112
|
-
* 200:
|
|
113
|
-
* description: Agent response with tool-use steps
|
|
114
|
-
*/
|
|
115
|
-
router.post('/agent', agentHandler);
|
|
116
|
-
|
|
117
|
-
module.exports = router;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
const { getModel } = require('../config/ai');
|
|
2
|
-
const { HumanMessage, SystemMessage, AIMessage } = require('@langchain/core/messages');
|
|
3
|
-
const { StringOutputParser } = require('@langchain/core/output_parsers');
|
|
4
|
-
const { ChatPromptTemplate } = require('@langchain/core/prompts');
|
|
5
|
-
const { logger } = require('../utils/logger');
|
|
6
|
-
|
|
7
|
-
const DEFAULT_SYSTEM_PROMPT = 'You are a helpful AI assistant.';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Send a chat message and get a complete response.
|
|
11
|
-
*/
|
|
12
|
-
const chat = async (message, options = {}) => {
|
|
13
|
-
const model = getModel(options);
|
|
14
|
-
const messages = [
|
|
15
|
-
new SystemMessage(options.systemPrompt || DEFAULT_SYSTEM_PROMPT),
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
if (Array.isArray(options.history)) {
|
|
19
|
-
for (const msg of options.history) {
|
|
20
|
-
if (msg.role === 'user') messages.push(new HumanMessage(msg.content));
|
|
21
|
-
else if (msg.role === 'assistant') messages.push(new AIMessage(msg.content));
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
messages.push(new HumanMessage(message));
|
|
26
|
-
|
|
27
|
-
const response = await model.invoke(messages);
|
|
28
|
-
|
|
29
|
-
logger.info('AI chat completed', {
|
|
30
|
-
provider: process.env.AI_PROVIDER,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
content: response.content,
|
|
35
|
-
model: response.response_metadata?.model || 'unknown',
|
|
36
|
-
usage: response.usage_metadata || null,
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Stream a chat response. Returns an async generator yielding text chunks.
|
|
42
|
-
*/
|
|
43
|
-
const stream = async function* (message, options = {}) {
|
|
44
|
-
const model = getModel(options);
|
|
45
|
-
const messages = [
|
|
46
|
-
new SystemMessage(options.systemPrompt || DEFAULT_SYSTEM_PROMPT),
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
if (Array.isArray(options.history)) {
|
|
50
|
-
for (const msg of options.history) {
|
|
51
|
-
if (msg.role === 'user') messages.push(new HumanMessage(msg.content));
|
|
52
|
-
else if (msg.role === 'assistant') messages.push(new AIMessage(msg.content));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
messages.push(new HumanMessage(message));
|
|
57
|
-
|
|
58
|
-
const streamResponse = await model.stream(messages);
|
|
59
|
-
|
|
60
|
-
for await (const chunk of streamResponse) {
|
|
61
|
-
if (chunk.content) {
|
|
62
|
-
yield chunk.content;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Run a prompt template chain with variable substitution.
|
|
69
|
-
*/
|
|
70
|
-
const chain = async (template, variables = {}, options = {}) => {
|
|
71
|
-
const model = getModel(options);
|
|
72
|
-
const prompt = ChatPromptTemplate.fromTemplate(template);
|
|
73
|
-
const outputParser = new StringOutputParser();
|
|
74
|
-
const pipeline = prompt.pipe(model).pipe(outputParser);
|
|
75
|
-
|
|
76
|
-
const result = await pipeline.invoke(variables);
|
|
77
|
-
return result;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
module.exports = { chat, stream, chain };
|