epa-testeprojetoia 0.1.0

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.
Files changed (100) hide show
  1. package/.idea/epa_mcp.iml +8 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/php.xml +19 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/AGENTS.md +10 -0
  6. package/README.md +339 -0
  7. package/dist/agent/agentHelpers.js +21 -0
  8. package/dist/agent/openaiAgent.js +170 -0
  9. package/dist/agent/slidingWindow.js +13 -0
  10. package/dist/api/epaApiClient.js +59 -0
  11. package/dist/cli/index.js +47 -0
  12. package/dist/config/credentialStore.js +92 -0
  13. package/dist/config/ensureCliAuth.js +127 -0
  14. package/dist/config/loadConfig.js +40 -0
  15. package/dist/config/setup.js +23 -0
  16. package/dist/core/createReferenceTool.js +12 -0
  17. package/dist/core/createTool.js +32 -0
  18. package/dist/mocks/requestMocks.js +47 -0
  19. package/dist/server/server.js +41 -0
  20. package/dist/server/stdioServer.js +3 -0
  21. package/dist/services/request/requestFilters.js +1 -0
  22. package/dist/services/requestService.js +81 -0
  23. package/dist/services/teamService.js +18 -0
  24. package/dist/sql/createSqlConnection.js +13 -0
  25. package/dist/sql/fetchSchemaSummary.js +38 -0
  26. package/dist/sql/generateSqlFromQuestion.js +83 -0
  27. package/dist/sql/generateSqlPlan.js +111 -0
  28. package/dist/sql/getSqlAgentErrorMessage.js +15 -0
  29. package/dist/sql/loadSqlAgentConfig.js +24 -0
  30. package/dist/sql/loadSqlConfig.js +43 -0
  31. package/dist/sql/parseSqlQuestionHints.js +29 -0
  32. package/dist/sql/runSqlAgentCli.js +163 -0
  33. package/dist/sql/runSqlCli.js +34 -0
  34. package/dist/sql/selectRelevantTables.js +136 -0
  35. package/dist/sql/sqlGuard.js +21 -0
  36. package/dist/sql/sqlPlan.js +16 -0
  37. package/dist/tests/requestService.test.js +110 -0
  38. package/dist/tools/analytics/teamReport.draft.js +16 -0
  39. package/dist/tools/loadTools.js +40 -0
  40. package/dist/tools/requests/assignees.js +50 -0
  41. package/dist/tools/requests/clients.draft.js +10 -0
  42. package/dist/tools/requests/create.draft.js +51 -0
  43. package/dist/tools/requests/list.js +50 -0
  44. package/dist/tools/requests/priorities.js +2 -0
  45. package/dist/tools/requests/services.draft.js +20 -0
  46. package/dist/tools/requests/types.js +16 -0
  47. package/dist/tools/requests/units.draft.js +10 -0
  48. package/dist/tools/requests/view.draft.js +20 -0
  49. package/dist/utils/buildDateRange.js +18 -0
  50. package/dist/utils/findIdByDescription.js +4 -0
  51. package/dist/utils/resolveAssigneeId.js +14 -0
  52. package/dist/utils/toolNameMaps.js +23 -0
  53. package/package.json +31 -0
  54. package/src/agent/agentHelpers.ts +25 -0
  55. package/src/agent/openaiAgent.ts +205 -0
  56. package/src/agent/slidingWindow.ts +17 -0
  57. package/src/api/epaApiClient.ts +82 -0
  58. package/src/cli/index.ts +61 -0
  59. package/src/config/credentialStore.ts +130 -0
  60. package/src/config/ensureCliAuth.ts +152 -0
  61. package/src/config/loadConfig.ts +62 -0
  62. package/src/config/setup.ts +35 -0
  63. package/src/core/createReferenceTool.ts +17 -0
  64. package/src/core/createTool.ts +51 -0
  65. package/src/mocks/requestMocks.ts +52 -0
  66. package/src/server/server.ts +61 -0
  67. package/src/server/stdioServer.ts +5 -0
  68. package/src/services/request/requestFilters.ts +12 -0
  69. package/src/services/requestService.ts +126 -0
  70. package/src/services/teamService.ts +27 -0
  71. package/src/sql/createSqlConnection.ts +15 -0
  72. package/src/sql/fetchSchemaSummary.ts +64 -0
  73. package/src/sql/generateSqlFromQuestion.ts +105 -0
  74. package/src/sql/generateSqlPlan.ts +133 -0
  75. package/src/sql/getSqlAgentErrorMessage.ts +24 -0
  76. package/src/sql/loadSqlAgentConfig.ts +33 -0
  77. package/src/sql/loadSqlConfig.ts +75 -0
  78. package/src/sql/parseSqlQuestionHints.ts +46 -0
  79. package/src/sql/runSqlAgentCli.ts +204 -0
  80. package/src/sql/runSqlCli.ts +40 -0
  81. package/src/sql/selectRelevantTables.ts +184 -0
  82. package/src/sql/sqlGuard.ts +28 -0
  83. package/src/sql/sqlPlan.ts +28 -0
  84. package/src/tests/requestService.test.ts +152 -0
  85. package/src/tools/analytics/teamReport.draft.ts +25 -0
  86. package/src/tools/loadTools.ts +59 -0
  87. package/src/tools/requests/assignees.ts +59 -0
  88. package/src/tools/requests/clients.draft.ts +18 -0
  89. package/src/tools/requests/create.draft.ts +59 -0
  90. package/src/tools/requests/list.ts +57 -0
  91. package/src/tools/requests/priorities.ts +6 -0
  92. package/src/tools/requests/services.draft.ts +24 -0
  93. package/src/tools/requests/types.ts +18 -0
  94. package/src/tools/requests/units.draft.ts +18 -0
  95. package/src/tools/requests/view.draft.ts +27 -0
  96. package/src/utils/buildDateRange.ts +22 -0
  97. package/src/utils/findIdByDescription.ts +10 -0
  98. package/src/utils/resolveAssigneeId.ts +24 -0
  99. package/src/utils/toolNameMaps.ts +33 -0
  100. package/tsconfig.json +11 -0
@@ -0,0 +1,35 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import readline from "readline";
4
+
5
+ export async function setupConfig() {
6
+
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout
10
+ });
11
+
12
+ const ask = (q: string) =>
13
+ new Promise<string>((resolve) => rl.question(q, resolve));
14
+
15
+ const apiUrl = await ask("EPA API URL: ");
16
+
17
+ rl.close();
18
+
19
+ const config = {
20
+ epaApiUrl: apiUrl,
21
+ epaApiTimeoutMs: 15000
22
+ };
23
+
24
+ const configDir = path.join(process.cwd(), "config");
25
+
26
+ if (!fs.existsSync(configDir)) {
27
+ fs.mkdirSync(configDir);
28
+ }
29
+
30
+ const configPath = path.join(configDir, "config.json");
31
+
32
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
33
+
34
+ console.log("Config salvo em:", configPath);
35
+ }
@@ -0,0 +1,17 @@
1
+ import { createTool } from "./createTool.js"
2
+ import { EpaApiClient } from "../api/epaApiClient.js"
3
+
4
+ const api = new EpaApiClient()
5
+
6
+ export function createReferenceTool(name:string, endpoint:string) {
7
+
8
+ return createTool({
9
+ name,
10
+ description: `Lista dados de referência: ${name}`,
11
+ handler: async () => {
12
+ return api.get(endpoint)
13
+ }
14
+
15
+ })
16
+
17
+ }
@@ -0,0 +1,51 @@
1
+ import { ZodTypeAny } from "zod"
2
+
3
+ export interface ToolDefinition {
4
+ name: string
5
+ description: string
6
+ schema?: any
7
+ validator?: ZodTypeAny
8
+ handler: (args: any) => Promise<any>
9
+ }
10
+
11
+ export function createTool(def: ToolDefinition) {
12
+
13
+ return {
14
+ name: def.name,
15
+ description: def.description,
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: def.schema || {},
19
+ },
20
+ async execute(args: any) {
21
+
22
+ let parsedArgs = args ?? {}
23
+
24
+ if (def.validator) {
25
+ const parsed = def.validator.safeParse(parsedArgs)
26
+
27
+ if (!parsed.success) {
28
+ const issues = parsed.error.issues
29
+ .map((issue) => `${issue.path.join(".") || "args"}: ${issue.message}`)
30
+ .join("; ")
31
+ throw new Error(`Parametros invalidos para "${def.name}": ${issues}`)
32
+ }
33
+
34
+ parsedArgs = parsed.data
35
+ }
36
+
37
+ const result = await def.handler(parsedArgs);
38
+
39
+ return {
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: JSON.stringify(result, null, 2)
44
+ }
45
+ ]
46
+ }
47
+
48
+ }
49
+ }
50
+
51
+ }
@@ -0,0 +1,52 @@
1
+ export const OS_TIPO = [
2
+ { "id": 1009, "descricao": "Abrir Lojas" },
3
+ { "id": 1005, "descricao": "Apoio Tecnológico" },
4
+ { "id": 1008, "descricao": "Chamados de TI" },
5
+ { "id": 1003, "descricao": "Desenvolvimento de Software" },
6
+ { "id": 1019, "descricao": "Engenharia Clínica" },
7
+ { "id": 999, "descricao": "Gestão da Qualidade" },
8
+ { "id": 1013, "descricao": "Gestão de Pessoas / DP" },
9
+ { "id": 1012, "descricao": "Instalação" },
10
+ { "id": 1010, "descricao": "Manutenção" },
11
+ { "id": 1002, "descricao": "Manutenção Preventiva" },
12
+ { "id": 1001, "descricao": "Ouvidoria" },
13
+ { "id": 1020, "descricao": "Qualidade - envio de documentos" },
14
+ { "id": 2, "descricao": "Reclamação" },
15
+ { "id": 1000, "descricao": "Recursos Humanos" },
16
+ { "id": 1015, "descricao": "Reembolso de despesas" },
17
+ { "id": 1007, "descricao": "Reembolso de Despesa_" },
18
+ { "id": 1018, "descricao": "Suporte Técnico de TI" },
19
+ { "id": 1014, "descricao": "Teste" },
20
+ { "id": 1017, "descricao": "teste" },
21
+ { "id": 1016, "descricao": "teste 123" },
22
+ { "id": 1, "descricao": "TI" }
23
+ ]
24
+
25
+ export const OS_TIPO_SERVICO = [
26
+ { "id": 1005, "descricao": "Analisar documento" },
27
+ { "id": 1007, "descricao": "Formatar padrão" },
28
+ { "id": 1016, "descricao": "Adicionar Campo" },
29
+ { "id": 1015, "descricao": "Criar Formulário" },
30
+ { "id": 1017, "descricao": "Criar Função" },
31
+ { "id": 995, "descricao": "Geração de Backup" }
32
+ ]
33
+
34
+ export const UNIDADE_GERENCIAL = [
35
+ { "id": 18, "descricao": "DESEMPENHO PLANEJAMENTO > TI" }
36
+ ]
37
+
38
+ export const RESPONSAVEL = [
39
+ { "id": 1, "descricao": "suporte.simeon" },
40
+ { "id": 577, "descricao": "maely.superusuario - Maely Teste Super Usuário" }
41
+ ]
42
+
43
+ export const CLIENTE = [
44
+ { "id": 1, "descricao": "Suporte Simeon" },
45
+ { "id": 349, "descricao": "Comercial-Simeon" }
46
+ ]
47
+
48
+ export const PRIORIDADE = [
49
+ { "id": 6, "descricao": "Média" },
50
+ { "id": 7, "descricao": "Alta" },
51
+ { "id": 8, "descricao": "Baixa" }
52
+ ]
@@ -0,0 +1,61 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { loadTools } from "../tools/loadTools.js";
5
+ import { buildToolNameMaps } from "../utils/toolNameMaps.js";
6
+
7
+ export async function createMcpServer() {
8
+
9
+ const server = new Server(
10
+ {
11
+ name: "epa-mcp",
12
+ version: "0.1.0"
13
+ },
14
+ {
15
+ capabilities: {
16
+ tools: {}
17
+ }
18
+ }
19
+ );
20
+
21
+ const tools = await loadTools();
22
+ const toolNames = tools.map((tool) => tool.name);
23
+ const { internalToExternal, externalToInternal } = buildToolNameMaps(toolNames);
24
+
25
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
26
+
27
+ return {
28
+ tools: tools.map((tool) => ({
29
+ name: internalToExternal.get(tool.name) ?? tool.name,
30
+ description: tool.description,
31
+ inputSchema: tool.inputSchema
32
+ }))
33
+ };
34
+
35
+ });
36
+
37
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
38
+
39
+ const internalName = externalToInternal.get(request.params.name) ?? request.params.name;
40
+ const tool = tools.find((t) => t.name === internalName);
41
+
42
+ if (!tool) {
43
+ throw new Error("Tool não encontrada");
44
+ }
45
+
46
+ return await tool.execute(request.params.arguments);
47
+
48
+ });
49
+
50
+ return server;
51
+ }
52
+
53
+ export async function startServer() {
54
+
55
+ const server = await createMcpServer()
56
+
57
+ const transport = new StdioServerTransport();
58
+
59
+ await server.connect(transport);
60
+
61
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startServer } from "./server.js"
4
+
5
+ await startServer()
@@ -0,0 +1,12 @@
1
+ export type AssigneeSearchOptions = {
2
+ q?: string
3
+ unitType?: string
4
+ unitValue?: number | string
5
+ }
6
+
7
+ export type ListPeriod = "current_year" | "last_12_months"
8
+
9
+ export type ListFilters = {
10
+ assigneeId?: number
11
+ period?: ListPeriod
12
+ }
@@ -0,0 +1,126 @@
1
+ import { EpaApiClient } from "../api/epaApiClient.js"
2
+ import type { AssigneeSearchOptions, ListFilters } from "./request/requestFilters.js"
3
+ import { buildDateRange } from "../utils/buildDateRange.js"
4
+
5
+ export class RequestService {
6
+
7
+ private api: EpaApiClient
8
+
9
+ constructor(apiClient?: EpaApiClient) {
10
+ this.api = apiClient ?? new EpaApiClient()
11
+ }
12
+
13
+ async getTypes() {
14
+ return this.api.get("/epa_os/ajax.php", {
15
+ action: "get",
16
+ controller: "TipoSolicitacao"
17
+ })
18
+ }
19
+
20
+ async getServices(typeId: number) {
21
+ return this.api.get("/api/tipos_servico", {
22
+ tipo_id: typeId
23
+ })
24
+ }
25
+
26
+ async getAssignees(term: string, options: AssigneeSearchOptions = {}) {
27
+
28
+ const params: Record<string, string | number> = {
29
+ term,
30
+ _type: "query",
31
+ q: options.q ?? term
32
+ }
33
+
34
+ if (options.unitValue !== undefined && options.unitValue !== null) {
35
+ params["filters[unidade_gerencial][type]"] = options.unitType ?? "pertenco"
36
+ params["filters[unidade_gerencial][value]"] = options.unitValue
37
+ }
38
+
39
+ const response = await this.api.get("/api/api/usuarios/search", params)
40
+
41
+ if (response && Array.isArray((response as any).results)) {
42
+ return (response as any).results
43
+ }
44
+
45
+ return response
46
+ }
47
+
48
+ async getClients() {
49
+ return this.api.get("/api/clientes")
50
+ }
51
+
52
+ async getUnits() {
53
+ return this.api.get("/api/unidades_gerenciais")
54
+ }
55
+
56
+ async list(filters: number | ListFilters = {}) {
57
+
58
+ const normalizedFilters: ListFilters =
59
+ typeof filters === "number" ? { assigneeId: filters } : filters
60
+
61
+ const params: any = {
62
+ data_inclusao: buildDateRange(normalizedFilters.period)
63
+ }
64
+
65
+ if (normalizedFilters.assigneeId) {
66
+ params.responsavel = normalizedFilters.assigneeId
67
+ }
68
+
69
+ const response = await this.api.post(
70
+ "/api/api/os/listar",
71
+ {},
72
+ params
73
+ )
74
+
75
+ if (!response || response.length === 0) {
76
+ return "Nenhuma solicitacao encontrada."
77
+ }
78
+
79
+ return response
80
+
81
+ }
82
+
83
+ async view(id: number) {
84
+
85
+ const response = await this.api.post(
86
+ "/api/api/os/listar",
87
+ {},
88
+ { codigo: id }
89
+ )
90
+
91
+ if (!response || response.length === 0) {
92
+ return `Solicitacao ${id} nao encontrada.`
93
+ }
94
+
95
+ if (Array.isArray(response)) {
96
+ const found = response.find((item: any) => Number(item.codigo ?? item.id) === id)
97
+ return found ?? response[0]
98
+ }
99
+
100
+ return response
101
+
102
+ }
103
+
104
+ async create(data: any) {
105
+
106
+ const payload = {
107
+ os_tipo: data.os_tipo,
108
+ os_tipo_servico: data.os_tipo_servico,
109
+ unidade_gerencial_executora: data.unidade_gerencial_executora,
110
+ responsavel: data.responsavel,
111
+ titulo: data.titulo,
112
+ cliente: data.cliente,
113
+ data_desejavel: data.data_desejavel,
114
+ prioridade: data.prioridade,
115
+ observacao: data.observacao
116
+ }
117
+
118
+ const result = await this.api.post(
119
+ "/api/api/solicitacao_rapida",
120
+ payload
121
+ )
122
+
123
+ return result
124
+ }
125
+
126
+ }
@@ -0,0 +1,27 @@
1
+ import { EpaApiClient } from "../api/epaApiClient.js";
2
+
3
+ export class TeamService {
4
+
5
+ private api: EpaApiClient;
6
+
7
+ constructor() {
8
+ this.api = new EpaApiClient();
9
+ }
10
+
11
+ async teamReport(name: string) {
12
+
13
+ const requests = await this.api.get("/solicitacoes", { responsavel: name });
14
+
15
+ const total = requests.length;
16
+
17
+ const overdue = requests.filter((request: any) => request.vencida);
18
+
19
+ return {
20
+ desenvolvedor: name,
21
+ total,
22
+ vencidas: overdue.length,
23
+ solicitacoes: requests
24
+ };
25
+ }
26
+
27
+ }
@@ -0,0 +1,15 @@
1
+ import mysql from "mysql2/promise"
2
+ import { loadSqlConfig } from "./loadSqlConfig.js"
3
+
4
+ export async function createSqlConnection() {
5
+ const sqlConfig = loadSqlConfig()
6
+
7
+ return mysql.createConnection({
8
+ host: sqlConfig.host,
9
+ port: sqlConfig.port,
10
+ database: sqlConfig.database,
11
+ user: sqlConfig.user,
12
+ password: sqlConfig.password,
13
+ ssl: sqlConfig.ssl ? {} : undefined
14
+ })
15
+ }
@@ -0,0 +1,64 @@
1
+ import type { RowDataPacket } from "mysql2"
2
+ import type { Connection } from "mysql2/promise"
3
+ import { loadSqlConfig } from "./loadSqlConfig.js"
4
+
5
+ type TableRow = RowDataPacket & {
6
+ TABLE_NAME: string
7
+ }
8
+
9
+ type ColumnRow = RowDataPacket & {
10
+ TABLE_NAME: string
11
+ COLUMN_NAME: string
12
+ DATA_TYPE: string
13
+ }
14
+
15
+ const MAX_COLUMNS_PER_TABLE = 20
16
+
17
+ export async function fetchAvailableTableNames(connection: Connection): Promise<string[]> {
18
+ const sqlConfig = loadSqlConfig()
19
+
20
+ const [tableRows] = await connection.query<TableRow[]>(
21
+ `
22
+ SELECT TABLE_NAME
23
+ FROM INFORMATION_SCHEMA.TABLES
24
+ WHERE TABLE_SCHEMA = ?
25
+ AND TABLE_TYPE = 'BASE TABLE'
26
+ ORDER BY TABLE_NAME
27
+ `,
28
+ [sqlConfig.database]
29
+ )
30
+
31
+ return tableRows.map((row) => row.TABLE_NAME)
32
+ }
33
+
34
+ export async function fetchSchemaSummary(connection: Connection, tableNames: string[]): Promise<string> {
35
+ const sqlConfig = loadSqlConfig()
36
+ const placeholders = tableNames.map(() => "?").join(", ")
37
+ const [columnRows] = await connection.query<ColumnRow[]>(
38
+ `
39
+ SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
40
+ FROM INFORMATION_SCHEMA.COLUMNS
41
+ WHERE TABLE_SCHEMA = ?
42
+ AND TABLE_NAME IN (${placeholders})
43
+ ORDER BY TABLE_NAME, ORDINAL_POSITION
44
+ `,
45
+ [sqlConfig.database, ...tableNames]
46
+ )
47
+
48
+ const columnsByTable = new Map<string, string[]>()
49
+
50
+ for (const row of columnRows) {
51
+ const columns = columnsByTable.get(row.TABLE_NAME) ?? []
52
+ if (columns.length < MAX_COLUMNS_PER_TABLE) {
53
+ columns.push(`${row.COLUMN_NAME} (${row.DATA_TYPE})`)
54
+ }
55
+ columnsByTable.set(row.TABLE_NAME, columns)
56
+ }
57
+
58
+ return tableNames
59
+ .map((tableName) => {
60
+ const columns = columnsByTable.get(tableName) ?? []
61
+ return `Tabela ${tableName}: ${columns.join(", ")}`
62
+ })
63
+ .join("\n")
64
+ }
@@ -0,0 +1,105 @@
1
+ import OpenAI from "openai"
2
+ import { loadSqlAgentConfig } from "./loadSqlAgentConfig.js"
3
+ import type { SqlQuestionHints } from "./parseSqlQuestionHints.js"
4
+ import type { SqlQueryPlan } from "./sqlPlan.js"
5
+
6
+ function extractSql(content: string): string {
7
+ const fencedMatch = content.match(/```(?:sql)?\s*([\s\S]*?)```/i)
8
+ if (fencedMatch) {
9
+ return fencedMatch[1].trim()
10
+ }
11
+
12
+ return content.trim()
13
+ }
14
+
15
+ function buildHintsSummary(hints: SqlQuestionHints) {
16
+ const lines: string[] = []
17
+
18
+ if (hints.tableNames.length > 0) {
19
+ lines.push(`Tabelas indicadas pelo usuario: ${hints.tableNames.join(", ")}`)
20
+ }
21
+
22
+ if (hints.fieldNames.length > 0) {
23
+ lines.push(`Campos indicados pelo usuario: ${hints.fieldNames.join(", ")}`)
24
+ }
25
+
26
+ if (hints.filters.length > 0) {
27
+ lines.push(`Filtros indicados pelo usuario: ${hints.filters.join(", ")}`)
28
+ }
29
+
30
+ if (hints.orderBy.length > 0) {
31
+ lines.push(`Ordenacao indicada pelo usuario: ${hints.orderBy.join(", ")}`)
32
+ }
33
+
34
+ if (hints.limit) {
35
+ lines.push(`Limite indicado pelo usuario: ${hints.limit}`)
36
+ }
37
+
38
+ return lines.length > 0 ? lines.join("\n") : "Nenhuma pista explicita informada."
39
+ }
40
+
41
+ export async function generateSqlFromQuestion(
42
+ question: string,
43
+ schemaSummary: string,
44
+ hints: SqlQuestionHints,
45
+ plan: SqlQueryPlan,
46
+ additionalGuidance?: string
47
+ ) {
48
+ const sqlAgentConfig = loadSqlAgentConfig()
49
+ const openai = new OpenAI({ apiKey: sqlAgentConfig.openaiApiKey })
50
+
51
+ const completion = await openai.chat.completions.create({
52
+ model: sqlAgentConfig.model,
53
+ temperature: 0,
54
+ messages: [
55
+ {
56
+ role: "system",
57
+ content: `
58
+ Voce e um assistente tecnico que gera SQL para MySQL/MariaDB.
59
+
60
+ Regras obrigatorias:
61
+ - gere apenas uma consulta SQL
62
+ - responda somente com SQL, sem explicacoes
63
+ - use apenas SELECT
64
+ - nao finalize a resposta com ponto e virgula
65
+ - nunca use INSERT, UPDATE, DELETE, ALTER, DROP, TRUNCATE, CREATE, REPLACE, GRANT, REVOKE, EXECUTE ou CALL
66
+ - quando o pedido for listar registros e o usuario nao informar quantidade, aplique LIMIT 10
67
+ - use somente tabelas e colunas presentes no schema informado
68
+ - priorize tabelas com nome diretamente relacionado ao pedido do usuario
69
+ - se houver tabela com nome muito proximo ao termo pedido, prefira essa tabela em vez de nomes genericos ou sem relacao clara
70
+ - quando o usuario informar explicitamente nomes de tabelas ou campos, trate essa informacao como instrucao prioritaria
71
+ - quando o usuario indicar um campo de relacionamento e outra tabela para vinculo, gere o JOIN usando essas pistas, desde que as colunas existam no schema informado
72
+ - quando o usuario indicar filtros, ordenacao ou limite, respeite essas instrucoes na SQL final
73
+ - siga o plano de consulta aprovado pelo usuario
74
+ `.trim()
75
+ },
76
+ {
77
+ role: "user",
78
+ content: `
79
+ Schema disponivel:
80
+ ${schemaSummary}
81
+
82
+ Pistas explicitas do usuario:
83
+ ${buildHintsSummary(hints)}
84
+
85
+ Plano aprovado:
86
+ ${JSON.stringify(plan, null, 2)}
87
+
88
+ Orientacoes adicionais:
89
+ ${additionalGuidance?.trim() || "Nenhuma orientacao adicional."}
90
+
91
+ Pedido do usuario:
92
+ ${question}
93
+ `.trim()
94
+ }
95
+ ]
96
+ })
97
+
98
+ const content = completion.choices[0]?.message?.content ?? ""
99
+
100
+ if (!content.trim()) {
101
+ throw new Error("A LLM nao retornou SQL para o pedido informado.")
102
+ }
103
+
104
+ return extractSql(content)
105
+ }