-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path1.agents.ts
More file actions
280 lines (248 loc) · 12.5 KB
/
1.agents.ts
File metadata and controls
280 lines (248 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// 📦 Modulos y dependencias
import { marked } from "marked";
import { markedTerminal } from "marked-terminal"
import { CopilotClient, defineTool, SessionConfig } from "@github/copilot-sdk";
import chalk from "chalk";
import ora from "ora";
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
marked.use(markedTerminal() as any);
// 🔧 Configuración general
const DEFAULT_MODEL = "gpt-5.2"; // 🧠 Modelo por defecto (ajusta según disponibilidad)
const YOUTUBE_CHANNEL_ID = "UC140iBrEZbOtvxWsJ-Tb0lQ" as const; // 📺 Canal de YouTube
let DEBUG_LOG_RESPONSE = false; // 🐞 Cambia a true para depurar el payload (o usa /debug)
// 🧰 Helpers (azúcar sintáctico con spinner y colorines)
const withSpinner = async <T>(text: string, action: () => Promise<T>) => {
const spinner = ora({
text: chalk.cyan(text),
spinner: "dots",
stream: process.stdout,
discardStdin: false,
}).start();
try {
const result = await action();
spinner.succeed(chalk.green(`✓ ${text}`));
return result;
} catch (error) {
spinner.fail(chalk.red(`✗ ${text}`));
throw error;
}
};
// 🛠️ Tools de ejemplo
// 🗓️ Tool simple que devuelve la fecha actual en formato ISO (UTC)
const getUtcDateTool = defineTool("get_utc_date", {
description: "Devuelve la fecha actual en formato ISO (UTC).",
parameters: {
type: "object",
properties: {},
},
handler: async () => new Date().toISOString(),
});
// 📡 Tool que obtiene el feed RSS de un canal de YouTube (simplificada, sin parsear XML)
const getYouTubeRSSFeedTool = defineTool("get_youtube_feed", {
description: "Obtiene el feed RSS del canal de YouTube configurado.",
parameters: {
type: "object",
properties: {
limit: {
type: "number",
description: "Número máximo de videos a devolver (opcional).",
},
},
},
handler: async ({ limit }: { limit?: number }) => {
return await withSpinner("📥 Obteniendo feed de YouTube...", async () => {
const response = await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${YOUTUBE_CHANNEL_ID}`);
const feedText = await response.text();
if (limit && Number.isFinite(limit)) {
// ✂️ Recortar resultados si se indica límite
return feedText.split("<entry>").slice(0, limit + 1).join("<entry>");
}
return feedText;
});
},
});
// 🤖 Tool que lista los modelos disponibles en el cliente Copilot
const listAvailableModelsTool = defineTool("list_available_models", {
description: "Lista todos los modelos disponibles en el cliente Copilot con sus características, límites y información de billing.",
parameters: {
type: "object",
properties: {},
},
handler: async () => {
const models = await copilotClient.listModels();
return JSON.stringify(models, null, 2);
},
});
// 💬 Tool que lista las sesiones activas del cliente Copilot
const listActiveSessionsTool = defineTool("list_active_sessions", {
description: "Lista todas las sesiones activas en el cliente Copilot con información sobre el resumen de cada sesión.",
parameters: {
type: "object",
properties: {},
},
handler: async () => {
const sessions = await copilotClient.listSessions();
return JSON.stringify(sessions, null, 2);
},
});
// 🤖 Cliente Copilot
const copilotClient = new CopilotClient({ logLevel: "all" });
copilotClient.start().then(() => {
console.log(chalk.green("✓ Cliente Copilot iniciado correctamente"));
});
// ⚙️ Opciones de sesión (educativo: aquí puedes jugar con todo)
const sessionOptions: SessionConfig = {
systemMessage: {
content: `Eres un asistente que me ayuda a buscar vídeos en el canal de YouTube de returngis. Como parte de la respuesta debes incluir la fecha actual si te preguntan sobre videos.
Para preguntas relacionadas con GitHub usa el MCP Server. Indica si has usado alguna tool para conseguir la respuesta y cuál ha sido.
IMPORTANTE: Cuando uses las tools 'list_available_models' o 'list_active_sessions', formatea la respuesta de manera clara y visual con emojis. NO uses markdown (no uses # ni **). Estructura la información de forma que sea fácil de leer en una terminal. Omite fechas y horas en la información de sesiones.`,
},
model: DEFAULT_MODEL,
tools: [getUtcDateTool, getYouTubeRSSFeedTool, listAvailableModelsTool, listActiveSessionsTool],
mcpServers: {
filesystem: {
type: "local",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
tools: ["*"],
}
},
};
// 🖨️ Render de respuestas
async function printResponse(promptText: string, response: any) {
console.log(chalk.bgBlue.white(`\n${"=".repeat(50)}\n💬 RESPUESTA A: ${promptText}\n${"=".repeat(50)}\n`));
console.log(await marked.parse(response?.data.content || "⚠️ Sin contenido")); // 🧾 Render Markdown bonico
if (DEBUG_LOG_RESPONSE) {
console.dir(response, { depth: 4, colors: true });
}
console.log(`${"=".repeat(50)}\n`);
}
// 🚦 Manejo de cada prompt
async function handlePrompt(session: any, promptText: string) {
console.log(chalk.gray(`Pregunta: ${promptText}`));
// 🎡 Spinner por prompt
// ⚠️ Importante: discardStdin: false para no interferir con readline
const spinner = ora({
text: chalk.cyan(`🤔 Pensando...`),
stream: process.stdout,
discardStdin: false, // 🔑 Cambio clave: no descartar stdin para no bloquear readline
hideCursor: false
}).start();
try {
const response = await session.sendAndWait({
prompt: promptText,
//timeoutMs: 45000,
// attachments: [{ type: "file", path: "Gis.png" }], // opcional: añade archivos aquí
});
spinner.succeed(chalk.green(`✅ Respuesta recibida`));
await printResponse(promptText, response);
return { ok: true };
} catch (error: any) {
// 🚨 Clasificamos el error para dar mejor feedback al usuario
if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND') {
spinner.fail(chalk.red(`❌ Error de conexión`));
console.error(chalk.yellow(" 💡 Verifica tu conexión a internet"));
} else if (error?.message?.includes('timeout') || error?.message?.includes('idle')) {
spinner.fail(chalk.red(`⚠️ Timeout: La sesión expiró por inactividad`));
} else if (error?.status === 401 || error?.message?.includes('unauthorized')) {
spinner.fail(chalk.red(`🔒 Error de autenticación`));
console.error(chalk.yellow(" 💡 Verifica que tu token GH_TOKEN sea válido"));
} else if (error?.status === 429 || error?.message?.includes('rate limit')) {
spinner.fail(chalk.red(`🚫 Rate limit: Demasiadas peticiones`));
console.error(chalk.yellow(" 💡 Espera unos minutos antes de continuar"));
} else {
spinner.fail(chalk.red(`❌ Error procesando la petición`));
}
console.error(chalk.dim(" Detalles:"), error?.message || error);
return { ok: false, error };
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 📖 COMANDO DE AYUDA
// ═══════════════════════════════════════════════════════════════════════════
/**
* 📖 Muestra la ayuda del chat con comandos y herramientas disponibles
* Esta función es útil para que los usuarios descubran las capacidades del bot
*/
function showHelp() {
console.log(chalk.cyan("\n╔════════════════════════════════════════════════════════════╗"));
console.log(chalk.cyan("║ 📖 AYUDA DEL CHAT COPILOT ║"));
console.log(chalk.cyan("╚════════════════════════════════════════════════════════════╝\n"));
// Comandos disponibles
console.log(chalk.yellow("🎮 COMANDOS DISPONIBLES:"));
console.log(chalk.white(" /help → Muestra esta ayuda"));
console.log(chalk.white(" /debug → Activa/desactiva modo debug"));
console.log(chalk.white(" /exit → Sale del chat\n"));
// Herramientas (tools) disponibles
console.log(chalk.yellow("🛠️ HERRAMIENTAS DISPONIBLES:"));
console.log(chalk.white(" 📅 get_utc_date → Devuelve la fecha actual en UTC"));
console.log(chalk.white(" 📺 get_youtube_feed → Obtiene el feed RSS de YouTube"));
console.log(chalk.white(" 🤖 list_available_models → Lista los modelos de Copilot disponibles"));
console.log(chalk.white(" 💬 list_active_sessions → Lista las sesiones activas\n"));
// MCP Servers
console.log(chalk.yellow("🌐 SERVIDORES MCP CONECTADOS:"));
console.log(chalk.white(" 📁 filesystem → Acceso a archivos del proyecto"));
console.log(chalk.white(" 🐙 github → API de GitHub (repos, issues, PRs...)\n"));
// Ejemplos de uso
console.log(chalk.yellow("💬 EJEMPLOS DE PREGUNTAS:"));
console.log(chalk.gray(" • ¿Cuál es el último vídeo en el canal de returngis?"));
console.log(chalk.gray(" • Dame sugerencias para mejorar el código de este repo"));
console.log(chalk.gray(" • ¿Qué modelos tengo disponibles?"));
console.log(chalk.gray(" • Lista mis sesiones activas\n"));
}
// 💬 Chat loop
async function chat() {
// 🧵 Creamos la sesión antes del loop para mantener el contexto.
// Si quieres reiniciar sesión tras cada prompt, mueve esta línea dentro del loop.
let session = await copilotClient.createSession(sessionOptions);
console.log(session);
session.on((event) => {
if (DEBUG_LOG_RESPONSE)
console.log("Evento de sesión:", event);
});
// ⌨️ readline.createInterface: sencillo loop de chat
const rl = readline.createInterface({ input, output });
console.log(chalk.bold.blue("\n=== MODO CHAT ==="));
console.log(chalk.gray("Escribe tu pregunta, /help para ayuda o /exit para salir."));
while (true) {
const userInput = (await rl.question(chalk.magenta("> "))).trim();
if (!userInput) continue;
// 🎮 Manejo de comandos especiales
const command = userInput.toLowerCase();
if (command === "/exit" || command === "/salir") break;
if (command === "/help" || command === "/ayuda") {
showHelp();
continue;
}
if (command === "/debug") {
DEBUG_LOG_RESPONSE = !DEBUG_LOG_RESPONSE;
const status = DEBUG_LOG_RESPONSE ? "🟢 ACTIVADO" : "🔴 DESACTIVADO";
console.log(chalk.yellow(`\n🐞 Modo debug: ${status}\n`));
if (DEBUG_LOG_RESPONSE) {
console.log(chalk.dim(" Ahora verás los payloads completos de las respuestas"));
console.log(chalk.dim(" y los eventos de sesión en la consola.\n"));
}
continue;
}
const result = await handlePrompt(session, userInput);
if (!result.ok && result.error?.message?.includes?.('idle')) {
console.log(chalk.yellow("🔄 Reconectando: Reiniciando sesión tras timeout..."));
session = await copilotClient.createSession(sessionOptions);
console.log(chalk.green("✅ Sesión reiniciada correctamente"));
}
// 🧽 Limpieza de línea: vuelve a mostrar el prompt limpio tras cada respuesta
output.write("\n");
}
rl.close();
}
chat()
.catch((error) => {
console.error(chalk.red("Error en la ejecución principal"), error);
process.exitCode = 1;
})
.finally(async () => {
const stopSpinner = ora(chalk.cyan("🛑 Deteniendo cliente...")).start();
await copilotClient.stop();
stopSpinner.succeed(chalk.green("Cliente detenido correctamente"));
});