creado planilla-agent
Some checks failed
build-and-deploy / filter (push) Successful in 1s
build-and-deploy / build (push) Failing after 5s
build-and-deploy / deploy (push) Has been skipped

This commit is contained in:
2025-06-05 15:40:53 -06:00
parent d570eec221
commit 950404dd85
26 changed files with 2681 additions and 1534 deletions

View File

@@ -0,0 +1,2 @@
node_modules
npm-debug.log

View File

@@ -0,0 +1,17 @@
# ---------- Build stage ----------
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# ---------- Production stage ----------
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm install --production
ENV PORT=8001
EXPOSE 8001
CMD ["node", "dist/index.js"]

2426
conversation-layer-agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
{
"name": "conversation-layer-agent",
"version": "1.0.0",
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "nodemon --watch src --ext ts --exec \"ts-node src/index.ts\""
},
"dependencies": {
"express": "^4.18.2",
"@google/genai": "^1.4.0",
"@modelcontextprotocol/sdk": "^1.12.1",
"dotenv": "^16.5.0"
},
"devDependencies": {
"typescript": "^5.4.5",
"@types/node": "^20.11.19",
"@types/express": "^4.17.21",
"nodemon": "^3.1.10",
"ts-node": "^10.9.2"
}
}

View File

@@ -0,0 +1,185 @@
import express from 'express';
import { GoogleGenAI, mcpToTool } from '@google/genai';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import dotenv from 'dotenv';
dotenv.config();
interface Participant {
id: string;
name: string;
isMe: boolean;
isAdmin?: boolean;
}
interface Msg {
id: string;
from: string;
to: string;
ts: number;
type: 'chat' | 'image' | 'audio' | 'sticker' | 'doc';
text?: string;
mediaUrl?: string;
mentions?: string[];
meta: {
ack: number;
hasReaction: boolean;
isQuoted: boolean;
};
}
interface Conversation {
chatId: string;
title: string;
isGroup: boolean;
unreadCount: number;
participants: Participant[];
messages: Msg[];
createdAt: number;
}
const PORT = Number(process.env.PORT) || 8001;
const API_KEY = process.env.GEMINI_API_KEY || '';
console.log(`Using Gemini API key: ${API_KEY}`);
const genAI = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null;
const MCP_URL = process.env.MCP_URL || 'http://planilla.interno.com/mcp';
let mcpClient: Client | undefined;
let mcpTransport: StreamableHTTPClientTransport | undefined;
async function getMcpClient(): Promise<Client> {
if (!mcpClient) {
mcpClient = new Client({ name: 'planilla-client', version: '1.0.0' });
mcpTransport = new StreamableHTTPClientTransport(new URL(MCP_URL));
await mcpClient.connect(mcpTransport);
}
return mcpClient;
}
/**
* Descripción de alto nivel para que cualquier agente (humano o LLM) entienda y
* trabaje con el repositorio sin perder tiempo buscando contexto.
*/
const repoInfo = `
📦 RESUMEN
Este repo orquesta tres servicios complementarios:
1. whatsapp-router → Recibe webhooks de OpenWA y re-expide los mensajes al agente o la UI.
2. conversation-layer-agent → LLM que responde dudas sobre el código y ejecuta acciones.
3. chat-ui → Interfaz web mínima que conversa con el agente.
🗂 ESTRUCTURA CLAVE
├─ docker-compose.yml # Levanta todo el stack
├─ whatsapp-router/
│ ├─ src/chatHandlers.ts # Mapeo chatId → handler; ¡editá aquí para nuevos agentes!
│ └─ … # Lógica de ruteo y validaciones
├─ conversation-layer-agent/
│ ├─ src/index.ts # Entrada principal del agente
│ └─ prompts/system.ts # Prompt base; importa repoInfo
└─ chat-ui/ # Frontend Vite + React (TypeScript)
└─ …
⚙️ VARIABLES DE ENTORNO (ejemplo .env)
OPEN_WA_URL=http://openwa:8080
LLM_AGENT_URL=http://conversation-layer-agent:8000
PORT=3001 # Puerto del router
NODE_ENV=development # Cambiá a production para desactivar logs verbosos
🚀 CÓMO LEVANTAR
1) cp .env.example .env && edítalo según tu entorno
2) docker-compose up -d --build
3) Visita http://localhost:3000 (UI) o revisá logs con \`docker-compose logs -f\`.
🔄 FLUJO DE MENSAJES
OpenWA → whatsapp-router (/webhook) → handler ↔ conversation-layer-agent ↔ chat-ui
🤖 GUÍA RÁPIDA PARA AGENTES LLM
- Responde corto, en el tono del usuario (“vos”, español hondureño).
- Usa SOLO la info del objeto \`Conversation\`; no mantengas estado entre turnos.
- Si falta contexto, pedilo en una línea.
- No repitas instrucciones ni digas que sos IA.
- Devuelve \`{ reply: string, actions?: any[] }\` (JSON puro) para facilitar parsing.
🔧 COMANDOS ÚTILES
npm run dev # Hot-reload del router
npm test # Ejecuta los tests
docker exec -it openwa sh # Shell dentro del contenedor OpenWA
git remote -v # Confirma remotos (origin: Gitea, github: mirror)
🔐 SEGURIDAD
- Los tokens/API keys van en variables de entorno; nunca los subas al repo.
- Usa certificados válidos o \`NODE_TLS_REJECT_UNAUTHORIZED=0\` SOLO en dev.
✍️ CONTRIBUCIONES
Push a rama feature/* → CI/CD en Gitea valida lint, tests y build.
Crea PR para revisión; no mezcles cambios de lógica y formato en el mismo commit.
📜 LICENCIA
GPL-3.0 — libre de usar, modificar y redistribuir mientras mantengas la misma licencia.
¡Listo! Con esto cualquier agente debería orientarse y empezar a trabajar sin drama.
`;
const app = express();
app.use(express.json());
app.post('/', async (req, res) => {
const conversation = req.body?.conversation as Conversation | undefined;
if (!conversation) return res.status(400).json({ error: 'Missing conversation' });
const lastMsg = conversation.messages[conversation.messages.length - 1];
const message = lastMsg?.text || '';
const context = conversation.messages
.slice(-10)
.map((m) => {
const sender =
conversation.participants.find((p) => p.id === m.from)?.name || m.from;
const content = m.text || `[${m.type}]`;
return `${sender}: ${content}`;
})
.join('\n');
if (!genAI) {
return res.json({ reply: repoInfo });
}
try {
const contents = `Repo information: ${repoInfo}\nConversation:\n${context}\n`;
const config: any = {};
// if (message.toLowerCase().includes('/planilla')) {
if (true) {
console.log('Using Model Context Protocol tools ', MCP_URL);
const client = await getMcpClient();
config.tools = [mcpToTool(client)];
}
const result = await genAI.models.generateContent({
model: 'gemini-2.0-flash',
contents,
config,
});
const reply = (result.text || '').trim();
res.json({ reply });
} catch (err: any) {
console.error('Gemini error', err.message);
res.status(500).json({ error: 'Failed to generate reply' });
}
});
app.get('/', (req, res) => {
res.send(`
<h1>Conversation Layer Agent</h1>
<p>This service answers questions about the repository.</p>
<p>Send a POST request to / with a JSON body containing {"conversation": {...}}</p>
<p>Example: {"conversation": {"chatId": "123@c.us", "title": "Chat", "isGroup": false, "unreadCount": 0, "participants": [{"id": "123@c.us", "name": "Alice", "isMe": false}], "messages": [{"id": "m1", "from": "123@c.us", "to": "me@c.us", "ts": 0, "type": "chat", "text": "hello", "meta": {"ack":0,"hasReaction":false,"isQuoted":false}}]}}</p>
<p>It will respond with a JSON object containing {"reply": "the answer"}</p>
<p>Repository info: ${repoInfo}</p>
`);
}
);
app.listen(PORT, () => {
console.log(`conversation-layer-agent listening on ${PORT}`);
});

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"lib": ["es2020"],
"moduleResolution": "node"
},
"include": ["src"]
}