@openenthrium/oe-runtime-sdk 1.7.5

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 (91) hide show
  1. package/README.md +139 -0
  2. package/index.js +58 -0
  3. package/package.json +64 -0
  4. package/src/data/connectionTypes.json +18587 -0
  5. package/src/engine/index.js +183 -0
  6. package/src/engine/llm.js +71 -0
  7. package/src/engine/promptBuilder.js +38 -0
  8. package/src/index.js +156 -0
  9. package/src/middleware/auth.js +63 -0
  10. package/src/providers/embedding/index.js +69 -0
  11. package/src/providers/llm/index.js +136 -0
  12. package/src/routes/admin.js +686 -0
  13. package/src/routes/agents.js +193 -0
  14. package/src/routes/apiKeys.js +55 -0
  15. package/src/routes/audio.js +111 -0
  16. package/src/routes/auth.js +83 -0
  17. package/src/routes/chat.js +381 -0
  18. package/src/routes/connectors.js +435 -0
  19. package/src/routes/dashboard.js +244 -0
  20. package/src/routes/documents.js +443 -0
  21. package/src/routes/embed.js +103 -0
  22. package/src/routes/marketplace.js +49 -0
  23. package/src/routes/models.js +81 -0
  24. package/src/routes/oauth.js +423 -0
  25. package/src/routes/projects.js +238 -0
  26. package/src/routes/settings.js +45 -0
  27. package/src/routes/setup.js +42 -0
  28. package/src/routes/sso.js +218 -0
  29. package/src/routes/superadmin.js +51 -0
  30. package/src/routes/templates.js +75 -0
  31. package/src/routes/threads.js +53 -0
  32. package/src/routes/workspaces.js +464 -0
  33. package/src/telemetry/bootstrap.js +39 -0
  34. package/src/telemetry/registration.js +16 -0
  35. package/src/utils/activityLog.js +16 -0
  36. package/src/utils/agentChain.js +141 -0
  37. package/src/utils/buildVisualization.js +102 -0
  38. package/src/utils/dlpScanner.js +57 -0
  39. package/src/utils/ingestionQueue.js +240 -0
  40. package/src/utils/prepareConnectors.js +66 -0
  41. package/src/utils/rag/_settings.js +6 -0
  42. package/src/utils/rag/chroma.js +82 -0
  43. package/src/utils/rag/lancedb.js +103 -0
  44. package/src/utils/rag/milvus.js +116 -0
  45. package/src/utils/rag/pgvector.js +104 -0
  46. package/src/utils/rag/pinecone.js +71 -0
  47. package/src/utils/rag/qdrant.js +106 -0
  48. package/src/utils/rag/weaviate.js +137 -0
  49. package/src/utils/rag/zilliz.js +115 -0
  50. package/src/utils/scheduler.js +168 -0
  51. package/src/utils/tier.js +106 -0
  52. package/src/utils/tools/adapters/_template.js +74 -0
  53. package/src/utils/tools/adapters/box.js +71 -0
  54. package/src/utils/tools/adapters/confluence.js +80 -0
  55. package/src/utils/tools/adapters/database.js +215 -0
  56. package/src/utils/tools/adapters/dropbox.js +74 -0
  57. package/src/utils/tools/adapters/elasticsearch.js +90 -0
  58. package/src/utils/tools/adapters/filesystem.js +197 -0
  59. package/src/utils/tools/adapters/freshdesk.js +87 -0
  60. package/src/utils/tools/adapters/gdrive.js +326 -0
  61. package/src/utils/tools/adapters/github.js +169 -0
  62. package/src/utils/tools/adapters/gmail.js +157 -0
  63. package/src/utils/tools/adapters/graphql.js +73 -0
  64. package/src/utils/tools/adapters/hubspot.js +101 -0
  65. package/src/utils/tools/adapters/image-gen.js +120 -0
  66. package/src/utils/tools/adapters/jira.js +86 -0
  67. package/src/utils/tools/adapters/kafka.js +126 -0
  68. package/src/utils/tools/adapters/ldap.js +118 -0
  69. package/src/utils/tools/adapters/mcp-client.js +138 -0
  70. package/src/utils/tools/adapters/mongodb.js +119 -0
  71. package/src/utils/tools/adapters/mqtt.js +106 -0
  72. package/src/utils/tools/adapters/music-gen.js +118 -0
  73. package/src/utils/tools/adapters/notion.js +93 -0
  74. package/src/utils/tools/adapters/ocr.js +107 -0
  75. package/src/utils/tools/adapters/onedrive.js +72 -0
  76. package/src/utils/tools/adapters/redis.js +104 -0
  77. package/src/utils/tools/adapters/rest-api.js +119 -0
  78. package/src/utils/tools/adapters/s3.js +142 -0
  79. package/src/utils/tools/adapters/search.js +80 -0
  80. package/src/utils/tools/adapters/sftp.js +121 -0
  81. package/src/utils/tools/adapters/shell.js +97 -0
  82. package/src/utils/tools/adapters/slack.js +83 -0
  83. package/src/utils/tools/adapters/speech.js +160 -0
  84. package/src/utils/tools/adapters/ssh.js +113 -0
  85. package/src/utils/tools/adapters/video-gen.js +152 -0
  86. package/src/utils/tools/adapters/web3.js +111 -0
  87. package/src/utils/tools/adapters/zendesk.js +82 -0
  88. package/src/utils/tools/adapters/zoho-mail.js +246 -0
  89. package/src/utils/tools/registry.js +229 -0
  90. package/src/utils/vectorStore.js +68 -0
  91. package/src/utils/workflowEngine.js +4 -0
@@ -0,0 +1,80 @@
1
+ const axios = require("axios");
2
+
3
+ function cfg(connector) {
4
+ return connector.authConfig ? JSON.parse(connector.authConfig) : {};
5
+ }
6
+
7
+ function client({ domain, email, apiToken }) {
8
+ return axios.create({
9
+ baseURL: `https://${domain}/wiki/rest/api`,
10
+ auth: { username: email, password: apiToken },
11
+ headers: { "Content-Type": "application/json" },
12
+ });
13
+ }
14
+
15
+ const TOOLS = c => [
16
+ { action: "search_pages", desc: `Search Confluence pages via ${c.name}.`,
17
+ params: { query: { type: "string", description: "Search query." },
18
+ spaceKey: { type: "string", description: "Optional space key to limit search." } }, required: ["query"] },
19
+ { action: "get_page", desc: `Get content of a Confluence page by ID via ${c.name}.`,
20
+ params: { pageId: { type: "string", description: "Confluence page ID." } }, required: ["pageId"] },
21
+ { action: "list_spaces", desc: `List Confluence spaces via ${c.name}.`, params: {}, required: [] },
22
+ ];
23
+
24
+ function getToolDefinitions(connector) {
25
+ return TOOLS(connector).map(t => ({
26
+ type: "function",
27
+ function: { name: `conn_${connector.id}_${t.action}`, description: t.desc,
28
+ parameters: { type: "object", properties: t.params, required: t.required } },
29
+ }));
30
+ }
31
+
32
+ function getAnthropicToolDefinitions(connector) {
33
+ return TOOLS(connector).map(t => ({
34
+ name: `conn_${connector.id}_${t.action}`, description: t.desc,
35
+ input_schema: { type: "object", properties: t.params, required: t.required },
36
+ }));
37
+ }
38
+
39
+ async function executeTool(action, args, connector) {
40
+ const creds = cfg(connector);
41
+ if (!creds.domain || !creds.apiToken) return "Confluence not configured. Please add credentials in Integrations.";
42
+ const api = client(creds);
43
+
44
+ try {
45
+ if (action === "search_pages") {
46
+ const { query, spaceKey } = args;
47
+ let cql = `type=page AND text~"${query}"`;
48
+ if (spaceKey) cql += ` AND space.key="${spaceKey}"`;
49
+ const res = await api.get(`/content/search?cql=${encodeURIComponent(cql)}&limit=10`);
50
+ const results = res.data.results || [];
51
+ if (!results.length) return "No pages found.";
52
+ return results.map(r => `[${r.id}] ${r.title} (Space: ${r.space?.name})`).join("\n");
53
+ }
54
+
55
+ if (action === "get_page") {
56
+ const res = await api.get(`/content/${args.pageId}?expand=body.storage`);
57
+ const text = res.data.body?.storage?.value?.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim() || "";
58
+ return `Title: ${res.data.title}\n\n${text.slice(0, 3000)}`;
59
+ }
60
+
61
+ if (action === "list_spaces") {
62
+ const res = await api.get("/space?limit=25&type=global");
63
+ const spaces = res.data.results || [];
64
+ return spaces.map(s => `${s.key}: ${s.name}`).join("\n") || "No spaces found.";
65
+ }
66
+
67
+ return `Unknown Confluence action: ${action}`;
68
+ } catch (err) {
69
+ return `Confluence error: ${err.response?.data?.message || err.message}`;
70
+ }
71
+ }
72
+
73
+ async function testConnection(authConfig) {
74
+ try {
75
+ const res = await client(authConfig).get("/space?limit=1");
76
+ return Array.isArray(res.data.results);
77
+ } catch { return false; }
78
+ }
79
+
80
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool, testConnection };
@@ -0,0 +1,215 @@
1
+ const DEFAULT_OPS = ["SELECT", "WITH", "SHOW", "DESCRIBE", "EXPLAIN"];
2
+
3
+ function getAllowedOps(connector) {
4
+ try {
5
+ const cfg = connector.config ? JSON.parse(connector.config) : {};
6
+ return cfg.allowedOps?.length ? cfg.allowedOps : DEFAULT_OPS;
7
+ } catch { return DEFAULT_OPS; }
8
+ }
9
+
10
+ function isSafe(sql, allowedOps) {
11
+ const pattern = new RegExp(`^\\s*(${allowedOps.join("|")})\\s`, "i");
12
+ return pattern.test(sql.trim());
13
+ }
14
+
15
+ function getToolDefinitions(connector) {
16
+ const ops = getAllowedOps(connector);
17
+ return [{
18
+ type: "function",
19
+ function: {
20
+ name: `conn_${connector.id}_query`,
21
+ description: `Execute a SQL statement on the "${connector.name}" ${connector.type} database. Allowed operations: ${ops.join(", ")}.`,
22
+ parameters: {
23
+ type: "object",
24
+ properties: {
25
+ sql: { type: "string", description: `SQL statement using allowed operations: ${ops.join(", ")}.` }
26
+ },
27
+ required: ["sql"]
28
+ }
29
+ }
30
+ }];
31
+ }
32
+
33
+ // Anthropic tool_use format
34
+ function getAnthropicToolDefinitions(connector) {
35
+ const ops = getAllowedOps(connector);
36
+ return [{
37
+ name: `conn_${connector.id}_query`,
38
+ description: `Execute a SQL statement on the "${connector.name}" ${connector.type} database. Allowed operations: ${ops.join(", ")}.`,
39
+ input_schema: {
40
+ type: "object",
41
+ properties: {
42
+ sql: { type: "string", description: `SQL statement using allowed operations: ${ops.join(", ")}.` }
43
+ },
44
+ required: ["sql"]
45
+ }
46
+ }];
47
+ }
48
+
49
+ function applySafeLimit(sql, dialect, maxRows) {
50
+ const n = maxRows > 0 ? maxRows : 100;
51
+ const clean = sql.replace(/;+\s*$/, "").trim();
52
+ if (/\bLIMIT\b/i.test(clean)) return clean;
53
+ if (/\bTOP\s+\d+\b/i.test(clean)) return clean;
54
+ if (/FETCH\s+FIRST\b/i.test(clean)) return clean;
55
+ if (/\bROWNUM\b/i.test(clean)) return clean;
56
+ if (/^\s*SELECT\s+(?:COUNT|SUM|AVG|MIN|MAX)\s*\(/i.test(clean)) return clean;
57
+ if (dialect === "mssql") return clean.replace(/^(\s*SELECT\s+)/i, `$1TOP ${n} `);
58
+ if (dialect === "oracle") return clean + `\nFETCH FIRST ${n} ROWS ONLY`;
59
+ return clean + ` LIMIT ${n}`;
60
+ }
61
+
62
+ async function executeTool(action, args, connector) {
63
+ if (action !== "query") return "Unknown action.";
64
+
65
+ const sql = (args.sql || "").trim();
66
+ const allowedOps = getAllowedOps(connector);
67
+ if (!sql) return "No SQL provided.";
68
+ if (!isSafe(sql, allowedOps)) return `Operation not allowed. Permitted: ${allowedOps.join(", ")}.`;
69
+
70
+ const isSelect = /^\s*SELECT\b/i.test(sql);
71
+ const cfg = connector.config ? JSON.parse(connector.config) : {};
72
+ const auth = connector.authConfig ? JSON.parse(connector.authConfig) : {};
73
+ const maxRows = cfg.maxRows || 100;
74
+
75
+ try {
76
+ if (connector.type === "postgresql") {
77
+ const { Pool } = require("pg");
78
+ const pool = new Pool({
79
+ connectionString: cfg.url || undefined,
80
+ host: cfg.host || auth.host || "localhost",
81
+ port: parseInt(cfg.port || auth.port || "5432"),
82
+ database: cfg.database || auth.database || undefined,
83
+ user: auth.username || auth.user || cfg.user || undefined,
84
+ password: auth.password || cfg.password || undefined,
85
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : false,
86
+ connectionTimeoutMillis: 10000,
87
+ query_timeout: 30000,
88
+ });
89
+ const result = await pool.query(isSelect ? applySafeLimit(sql, "postgresql", maxRows) : sql);
90
+ await pool.end();
91
+ if (!result.rows.length) return "Query returned no results.";
92
+ return JSON.stringify(result.rows, null, 2);
93
+ }
94
+
95
+ if (connector.type === "mysql") {
96
+ const mysql = require("mysql2/promise");
97
+ const conn = await mysql.createConnection({
98
+ host: cfg.host || auth.host || "localhost",
99
+ port: parseInt(cfg.port || auth.port || "3306"),
100
+ database: cfg.database || auth.database || undefined,
101
+ user: auth.username || auth.user || cfg.user || undefined,
102
+ password: auth.password || cfg.password || undefined,
103
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined,
104
+ connectTimeout: 10000,
105
+ });
106
+ const [rows] = await conn.execute(isSelect ? applySafeLimit(sql, "mysql", maxRows) : sql);
107
+ await conn.end();
108
+ if (!rows.length) return "Query returned no results.";
109
+ return JSON.stringify(rows, null, 2);
110
+ }
111
+
112
+ if (connector.type === "mssql") {
113
+ const mssql = require("mssql");
114
+ const pool = await mssql.connect({
115
+ server: cfg.host || "localhost",
116
+ port: parseInt(cfg.port || "1433"),
117
+ database: cfg.database || undefined,
118
+ user: auth.username || undefined,
119
+ password: auth.password || undefined,
120
+ options: {
121
+ encrypt: cfg.encrypt !== false,
122
+ trustServerCertificate: cfg.trustServerCertificate || false,
123
+ },
124
+ connectionTimeout: 10000,
125
+ requestTimeout: 30000,
126
+ });
127
+ const result = await pool.request().query(isSelect ? applySafeLimit(sql, "mssql", maxRows) : sql);
128
+ await pool.close();
129
+ if (!result.recordset?.length) return "Query returned no results.";
130
+ return JSON.stringify(result.recordset, null, 2);
131
+ }
132
+
133
+ if (connector.type === "oracle") {
134
+ const oracledb = require("oracledb");
135
+ oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
136
+ const connectString = cfg.connectString ||
137
+ `${cfg.host || "localhost"}:${cfg.port || "1521"}/${cfg.serviceName || cfg.sid || "ORCL"}`;
138
+ const conn = await oracledb.getConnection({
139
+ user: auth.username || undefined,
140
+ password: auth.password || undefined,
141
+ connectString,
142
+ });
143
+ const result = await conn.execute(isSelect ? applySafeLimit(sql, "oracle", maxRows) : sql, [], { outFormat: oracledb.OUT_FORMAT_OBJECT });
144
+ await conn.close();
145
+ if (!result.rows?.length) return "Query returned no results.";
146
+ return JSON.stringify(result.rows, null, 2);
147
+ }
148
+
149
+ if (connector.type === "cockroachdb") {
150
+ const { Pool } = require("pg");
151
+ const pool = new Pool({
152
+ host: cfg.host || "localhost",
153
+ port: parseInt(cfg.port || "26257"),
154
+ database: cfg.database || "defaultdb",
155
+ user: auth.username || undefined,
156
+ password: auth.password || undefined,
157
+ ssl: cfg.ssl ? { rejectUnauthorized: false } : false,
158
+ connectionTimeoutMillis: 10000,
159
+ });
160
+ const result = await pool.query(isSelect ? applySafeLimit(sql, "postgresql", maxRows) : sql);
161
+ await pool.end();
162
+ if (!result.rows.length) return "Query returned no results.";
163
+ return JSON.stringify(result.rows, null, 2);
164
+ }
165
+
166
+ if (connector.type === "sqlite") {
167
+ const Database = require("better-sqlite3");
168
+ const db = new Database(cfg.filename || ":memory:", { readonly: isSelect });
169
+ const stmt = db.prepare(isSelect ? applySafeLimit(sql, "sqlite", maxRows) : sql);
170
+ const rows = isSelect ? stmt.all() : [stmt.run()];
171
+ db.close();
172
+ if (!rows.length) return "Query returned no results.";
173
+ return JSON.stringify(rows, null, 2);
174
+ }
175
+
176
+ if (connector.type === "snowflake") {
177
+ const snowflake = require("snowflake-sdk");
178
+ const conn = await new Promise((resolve, reject) => {
179
+ const c = snowflake.createConnection({
180
+ account: cfg.account,
181
+ username: auth.username,
182
+ password: auth.password,
183
+ database: cfg.database,
184
+ schema: cfg.schema || "PUBLIC",
185
+ warehouse: cfg.warehouse,
186
+ role: cfg.role || undefined,
187
+ });
188
+ c.connect(err => err ? reject(err) : resolve(c));
189
+ });
190
+ const rows = await new Promise((resolve, reject) => {
191
+ conn.execute({ sqlText: isSelect ? applySafeLimit(sql, "snowflake", maxRows) : sql,
192
+ complete: (err, _stmt, rows) => err ? reject(err) : resolve(rows || []) });
193
+ });
194
+ conn.destroy(() => {});
195
+ if (!rows.length) return "Query returned no results.";
196
+ return JSON.stringify(rows, null, 2);
197
+ }
198
+
199
+ if (connector.type === "bigquery") {
200
+ const { BigQuery } = require("@google-cloud/bigquery");
201
+ const credentials = auth.keyFileJson ? JSON.parse(auth.keyFileJson) : undefined;
202
+ const bq = new BigQuery({ projectId: cfg.projectId, credentials });
203
+ const [rows] = await bq.query({ query: isSelect ? applySafeLimit(sql, "bigquery", maxRows) : sql,
204
+ defaultDataset: cfg.dataset ? { datasetId: cfg.dataset, projectId: cfg.projectId } : undefined });
205
+ if (!rows.length) return "Query returned no results.";
206
+ return JSON.stringify(rows, null, 2);
207
+ }
208
+
209
+ return `Unsupported database type: ${connector.type}`;
210
+ } catch (err) {
211
+ return `Database error: ${err.message}`;
212
+ }
213
+ }
214
+
215
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool };
@@ -0,0 +1,74 @@
1
+ const axios = require("axios");
2
+
3
+ function auth(connector) { return connector.authConfig ? JSON.parse(connector.authConfig) : {}; }
4
+ function cfg(connector) { return connector.config ? JSON.parse(connector.config) : {}; }
5
+
6
+ async function getToken(connector, db) {
7
+ const a = auth(connector);
8
+ const c = cfg(connector);
9
+ if (!a.refreshToken) return a.accessToken;
10
+ if (a.expiresAt && Date.now() < a.expiresAt - 60000) return a.accessToken;
11
+ const { data } = await axios.post("https://api.dropboxapi.com/oauth2/token",
12
+ new URLSearchParams({ grant_type: "refresh_token", refresh_token: a.refreshToken, client_id: c.appKey, client_secret: c.appSecret }),
13
+ { headers: { "Content-Type": "application/x-www-form-urlencoded" } });
14
+ const newAuth = { ...a, accessToken: data.access_token, expiresAt: Date.now() + (data.expires_in || 14400) * 1000 };
15
+ if (db) await db.connector.update({ where: { id: connector.id }, data: { authConfig: JSON.stringify(newAuth) } });
16
+ return data.access_token;
17
+ }
18
+
19
+ function api(token) {
20
+ return axios.create({ baseURL: "https://api.dropboxapi.com/2", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
21
+ }
22
+
23
+ const TOOLS = c => [
24
+ { action: "list_files", desc: `List files/folders in Dropbox via ${c.name}.`,
25
+ params: { path: { type: "string", description: 'Folder path e.g. "/Documents" or "" for root.' } }, required: [] },
26
+ { action: "read_file", desc: `Read the text content of a Dropbox file via ${c.name}.`,
27
+ params: { path: { type: "string", description: "File path in Dropbox e.g. /Reports/q1.csv" } }, required: ["path"] },
28
+ { action: "search", desc: `Search files in Dropbox via ${c.name}.`,
29
+ params: { query: { type: "string", description: "Search query." } }, required: ["query"] },
30
+ ];
31
+
32
+ function getToolDefinitions(connector) {
33
+ return TOOLS(connector).map(t => ({ type: "function", function: { name: `conn_${connector.id}_${t.action}`, description: t.desc, parameters: { type: "object", properties: t.params, required: t.required } } }));
34
+ }
35
+ function getAnthropicToolDefinitions(connector) {
36
+ return TOOLS(connector).map(t => ({ name: `conn_${connector.id}_${t.action}`, description: t.desc, input_schema: { type: "object", properties: t.params, required: t.required } }));
37
+ }
38
+
39
+ async function executeTool(action, args, connector, db) {
40
+ const token = await getToken(connector, db);
41
+ try {
42
+ if (action === "list_files") {
43
+ const res = await api(token).post("/files/list_folder", { path: args.path || "", limit: 100 });
44
+ const entries = res.data.entries || [];
45
+ return entries.map(e => `${e[".tag"] === "folder" ? "📁" : "📄"} ${e.name} (path: ${e.path_display})`).join("\n") || "Empty folder.";
46
+ }
47
+ if (action === "read_file") {
48
+ const res = await axios.post("https://content.dropboxapi.com/2/files/download", null, {
49
+ headers: { Authorization: `Bearer ${token}`, "Dropbox-API-Arg": JSON.stringify({ path: args.path }) },
50
+ responseType: "text"
51
+ });
52
+ return String(res.data).slice(0, 8000);
53
+ }
54
+ if (action === "search") {
55
+ const res = await api(token).post("/files/search_v2", { query: args.query, options: { max_results: 20 } });
56
+ const matches = res.data.matches || [];
57
+ return matches.map(m => `${m.metadata?.metadata?.name} (${m.metadata?.metadata?.path_display})`).join("\n") || "No results.";
58
+ }
59
+ return `Unknown Dropbox action: ${action}`;
60
+ } catch (err) { return `Dropbox error: ${err.response?.data?.error_summary || err.message}`; }
61
+ }
62
+
63
+ async function testConnection(authConfig, config, db, connectorId) {
64
+ try {
65
+ const connector = { authConfig: JSON.stringify(authConfig), config: JSON.stringify(config || {}), id: connectorId };
66
+ const token = await getToken(connector, db);
67
+ const res = await axios.post("https://api.dropboxapi.com/2/users/get_current_account", null, {
68
+ headers: { Authorization: `Bearer ${token}` }
69
+ });
70
+ return !!res.data.account_id;
71
+ } catch { return false; }
72
+ }
73
+
74
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool, testConnection, getToken };
@@ -0,0 +1,90 @@
1
+ const { Client } = require("@elastic/elasticsearch");
2
+
3
+ function cfg(connector) {
4
+ return connector.config ? JSON.parse(connector.config) : {};
5
+ }
6
+ function auth(connector) {
7
+ return connector.authConfig ? JSON.parse(connector.authConfig) : {};
8
+ }
9
+
10
+ function client(connector) {
11
+ const c = cfg(connector);
12
+ const a = auth(connector);
13
+ const opts = { node: c.node || "http://localhost:9200", requestTimeout: 15000 };
14
+ if (a.apiKey) opts.auth = { apiKey: a.apiKey };
15
+ else if (a.username || a.password) opts.auth = { username: a.username, password: a.password };
16
+ return new Client(opts);
17
+ }
18
+
19
+ const TOOLS = c => {
20
+ const idx = JSON.parse(c.config || "{}").index || "your-index";
21
+ return [
22
+ { action: "search", desc: `Search documents in ${c.name} (Elasticsearch).`,
23
+ params: { index: { type: "string", description: `Index name (default: ${idx})` },
24
+ query: { type: "object", description: 'Elasticsearch query DSL e.g. {"match":{"field":"value"}}' },
25
+ size: { type: "number", description: "Max results (default 10)" } }, required: [] },
26
+ { action: "get", desc: `Get a document by ID from ${c.name}.`,
27
+ params: { index: { type: "string", description: `Index name (default: ${idx})` },
28
+ id: { type: "string", description: "Document ID." } }, required: ["id"] },
29
+ { action: "index_doc",desc: `Index (upsert) a document in ${c.name}.`,
30
+ params: { index: { type: "string", description: `Index name (default: ${idx})` },
31
+ id: { type: "string", description: "Document ID (optional, auto-generated if omitted)." },
32
+ document: { type: "object", description: "Document body to index." } }, required: ["document"] },
33
+ ];
34
+ };
35
+
36
+ function getToolDefinitions(connector) {
37
+ return TOOLS(connector).map(t => ({
38
+ type: "function",
39
+ function: { name: `conn_${connector.id}_${t.action}`, description: t.desc,
40
+ parameters: { type: "object", properties: t.params, required: t.required } },
41
+ }));
42
+ }
43
+
44
+ function getAnthropicToolDefinitions(connector) {
45
+ return TOOLS(connector).map(t => ({
46
+ name: `conn_${connector.id}_${t.action}`, description: t.desc,
47
+ input_schema: { type: "object", properties: t.params, required: t.required },
48
+ }));
49
+ }
50
+
51
+ async function executeTool(action, args, connector) {
52
+ const c = cfg(connector);
53
+ const es = client(connector);
54
+ const idx = args.index || c.index || "default";
55
+
56
+ try {
57
+ if (action === "search") {
58
+ const res = await es.search({ index: idx, size: args.size || 10,
59
+ body: args.query ? { query: args.query } : { query: { match_all: {} } } });
60
+ const hits = res.hits?.hits || [];
61
+ if (!hits.length) return "No documents found.";
62
+ return JSON.stringify(hits.map(h => ({ _id: h._id, _score: h._score, ...h._source })), null, 2);
63
+ }
64
+
65
+ if (action === "get") {
66
+ const res = await es.get({ index: idx, id: args.id });
67
+ return JSON.stringify({ _id: res._id, ...res._source }, null, 2);
68
+ }
69
+
70
+ if (action === "index_doc") {
71
+ const res = await es.index({ index: idx, id: args.id || undefined, document: args.document });
72
+ return `Document ${res.result}: _id=${res._id} in index "${idx}"`;
73
+ }
74
+
75
+ return `Unknown Elasticsearch action: ${action}`;
76
+ } catch (err) {
77
+ return `Elasticsearch error: ${err.message}`;
78
+ }
79
+ }
80
+
81
+ async function testConnection(authConfig, config) {
82
+ const connector = { config: JSON.stringify(config || {}), authConfig: JSON.stringify(authConfig || {}) };
83
+ try {
84
+ const es = client(connector);
85
+ await es.ping();
86
+ return true;
87
+ } catch { return false; }
88
+ }
89
+
90
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool, testConnection };
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ function cfg(connector) {
6
+ return connector.authConfig ? JSON.parse(connector.authConfig) : connector;
7
+ }
8
+
9
+ // Prevent directory traversal attacks
10
+ function safePath(basePath, filePath) {
11
+ const resolved = path.resolve(basePath, filePath);
12
+ if (!resolved.startsWith(path.resolve(basePath))) {
13
+ throw new Error(`Access denied: path is outside basePath (${basePath})`);
14
+ }
15
+ return resolved;
16
+ }
17
+
18
+ const TOOLS = c => [
19
+ {
20
+ action: "list_dir",
21
+ desc: `List files and folders in a directory on the local filesystem via ${c.name}.`,
22
+ params: {
23
+ dir: { type: "string", description: "Directory path relative to basePath. Use '.' for root." },
24
+ },
25
+ required: ["dir"],
26
+ },
27
+ {
28
+ action: "read_file",
29
+ desc: `Read the contents of a file on the local filesystem via ${c.name}.`,
30
+ params: {
31
+ file: { type: "string", description: "File path relative to basePath." },
32
+ },
33
+ required: ["file"],
34
+ },
35
+ {
36
+ action: "write_file",
37
+ desc: `Write or overwrite a file on the local filesystem via ${c.name}.`,
38
+ params: {
39
+ file: { type: "string", description: "File path relative to basePath." },
40
+ content: { type: "string", description: "Content to write." },
41
+ },
42
+ required: ["file", "content"],
43
+ },
44
+ {
45
+ action: "append_file",
46
+ desc: `Append content to an existing file on the local filesystem via ${c.name}.`,
47
+ params: {
48
+ file: { type: "string", description: "File path relative to basePath." },
49
+ content: { type: "string", description: "Content to append." },
50
+ },
51
+ required: ["file", "content"],
52
+ },
53
+ {
54
+ action: "delete_file",
55
+ desc: `Delete a file on the local filesystem via ${c.name}.`,
56
+ params: {
57
+ file: { type: "string", description: "File path relative to basePath." },
58
+ },
59
+ required: ["file"],
60
+ },
61
+ {
62
+ action: "make_dir",
63
+ desc: `Create a directory (and any missing parents) on the local filesystem via ${c.name}.`,
64
+ params: {
65
+ dir: { type: "string", description: "Directory path relative to basePath." },
66
+ },
67
+ required: ["dir"],
68
+ },
69
+ {
70
+ action: "file_info",
71
+ desc: `Get metadata (size, modified date, type) of a file or directory via ${c.name}.`,
72
+ params: {
73
+ file: { type: "string", description: "File or directory path relative to basePath." },
74
+ },
75
+ required: ["file"],
76
+ },
77
+ {
78
+ action: "search_files",
79
+ desc: `Search for files by name pattern recursively via ${c.name}.`,
80
+ params: {
81
+ pattern: { type: "string", description: "Filename pattern to match (e.g. '*.js', 'README*')." },
82
+ dir: { type: "string", description: "Directory to search in relative to basePath. Use '.' for all." },
83
+ },
84
+ required: ["pattern", "dir"],
85
+ },
86
+ ];
87
+
88
+ function getToolDefinitions(connector) {
89
+ return TOOLS(connector).map(t => ({
90
+ type: "function",
91
+ function: {
92
+ name: `conn_${connector.id}_${t.action}`,
93
+ description: t.desc,
94
+ parameters: { type: "object", properties: t.params, required: t.required },
95
+ },
96
+ }));
97
+ }
98
+
99
+ function getAnthropicToolDefinitions(connector) {
100
+ return TOOLS(connector).map(t => ({
101
+ name: `conn_${connector.id}_${t.action}`,
102
+ description: t.desc,
103
+ input_schema: { type: "object", properties: t.params, required: t.required },
104
+ }));
105
+ }
106
+
107
+ function matchPattern(filename, pattern) {
108
+ const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$", "i");
109
+ return regex.test(filename);
110
+ }
111
+
112
+ function searchRecursive(dir, pattern, results = []) {
113
+ try {
114
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
115
+ for (const entry of entries) {
116
+ if (entry.name.startsWith(".")) continue;
117
+ const fullPath = path.join(dir, entry.name);
118
+ if (matchPattern(entry.name, pattern)) results.push(fullPath);
119
+ if (entry.isDirectory()) searchRecursive(fullPath, pattern, results);
120
+ }
121
+ } catch {}
122
+ return results;
123
+ }
124
+
125
+ async function executeTool(action, args, connector) {
126
+ const { basePath } = cfg(connector);
127
+ if (!basePath) return "filesystem connector requires a basePath in config.";
128
+
129
+ try {
130
+ if (action === "list_dir") {
131
+ const target = safePath(basePath, args.dir || ".");
132
+ const entries = fs.readdirSync(target, { withFileTypes: true });
133
+ if (entries.length === 0) return "Directory is empty.";
134
+ return entries.map(e => `${e.isDirectory() ? "[dir] " : "[file]"} ${e.name}`).join("\n");
135
+ }
136
+
137
+ if (action === "read_file") {
138
+ const target = safePath(basePath, args.file);
139
+ if (!fs.existsSync(target)) return `File not found: ${args.file}`;
140
+ const content = fs.readFileSync(target, "utf8");
141
+ return content.length > 50000 ? content.slice(0, 50000) + "\n\n[truncated — file too large]" : content;
142
+ }
143
+
144
+ if (action === "write_file") {
145
+ const target = safePath(basePath, args.file);
146
+ fs.mkdirSync(path.dirname(target), { recursive: true });
147
+ fs.writeFileSync(target, args.content, "utf8");
148
+ return `Written: ${args.file}`;
149
+ }
150
+
151
+ if (action === "append_file") {
152
+ const target = safePath(basePath, args.file);
153
+ fs.mkdirSync(path.dirname(target), { recursive: true });
154
+ fs.appendFileSync(target, args.content, "utf8");
155
+ return `Appended to: ${args.file}`;
156
+ }
157
+
158
+ if (action === "delete_file") {
159
+ const target = safePath(basePath, args.file);
160
+ if (!fs.existsSync(target)) return `File not found: ${args.file}`;
161
+ fs.unlinkSync(target);
162
+ return `Deleted: ${args.file}`;
163
+ }
164
+
165
+ if (action === "make_dir") {
166
+ const target = safePath(basePath, args.dir);
167
+ fs.mkdirSync(target, { recursive: true });
168
+ return `Directory created: ${args.dir}`;
169
+ }
170
+
171
+ if (action === "file_info") {
172
+ const target = safePath(basePath, args.file);
173
+ if (!fs.existsSync(target)) return `Not found: ${args.file}`;
174
+ const stat = fs.statSync(target);
175
+ return JSON.stringify({
176
+ path: args.file,
177
+ type: stat.isDirectory() ? "directory" : "file",
178
+ size: `${(stat.size / 1024).toFixed(1)} KB`,
179
+ modified: stat.mtime.toISOString(),
180
+ created: stat.birthtime.toISOString(),
181
+ }, null, 2);
182
+ }
183
+
184
+ if (action === "search_files") {
185
+ const target = safePath(basePath, args.dir || ".");
186
+ const results = searchRecursive(target, args.pattern);
187
+ if (results.length === 0) return `No files matching "${args.pattern}" found.`;
188
+ return results.map(r => path.relative(basePath, r)).join("\n");
189
+ }
190
+
191
+ return `Unknown action: ${action}`;
192
+ } catch (err) {
193
+ return `Error: ${err.message}`;
194
+ }
195
+ }
196
+
197
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool };