@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,152 @@
1
+ "use strict";
2
+ const axios = require("axios");
3
+
4
+ function getToolDefinitions(connector) {
5
+ return [
6
+ {
7
+ type: "function",
8
+ function: {
9
+ name: `conn_${connector.id}_generate_video`,
10
+ description: `Generate a video from a text prompt or image using "${connector.name}". Returns a job ID or video URL (generation is async — use check_status to poll).`,
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ prompt: { type: "string", description: "Text description of the video to generate" },
15
+ image_url: { type: "string", description: "Optional: starting image URL for image-to-video generation" },
16
+ duration: { type: "number", description: "Video duration in seconds (provider limits apply, typically 4–16s)" },
17
+ ratio: { type: "string", description: "Aspect ratio: '16:9', '9:16', or '1:1'" },
18
+ },
19
+ required: ["prompt"],
20
+ },
21
+ },
22
+ },
23
+ {
24
+ type: "function",
25
+ function: {
26
+ name: `conn_${connector.id}_check_status`,
27
+ description: `Check the status of a video generation job from "${connector.name}". Pass the job_id returned by generate_video.`,
28
+ parameters: {
29
+ type: "object",
30
+ properties: {
31
+ job_id: { type: "string", description: "Job ID returned by generate_video" },
32
+ },
33
+ required: ["job_id"],
34
+ },
35
+ },
36
+ },
37
+ ];
38
+ }
39
+
40
+ function getAnthropicToolDefinitions(connector) {
41
+ return getToolDefinitions(connector).map(t => ({
42
+ name: t.function.name,
43
+ description: t.function.description,
44
+ input_schema: t.function.parameters,
45
+ }));
46
+ }
47
+
48
+ async function executeTool(action, args, connector) {
49
+ const auth = connector.authConfig ? JSON.parse(connector.authConfig) : {};
50
+ const cfg = connector.config ? JSON.parse(connector.config) : {};
51
+ const creds = { ...cfg, ...auth };
52
+ const type = connector.type;
53
+
54
+ // ── Runway ────────────────────────────────────────────────────────────────
55
+ if (type === "runway") {
56
+ if (action === "generate_video") {
57
+ const body = {
58
+ promptText: args.prompt,
59
+ model: "gen4_turbo",
60
+ ratio: args.ratio || "1280:720",
61
+ duration: args.duration || 5,
62
+ };
63
+ if (args.image_url) body.promptImage = args.image_url;
64
+ const resp = await axios.post(
65
+ "https://api.dev.runwayml.com/v1/image_to_video",
66
+ body,
67
+ { headers: { Authorization: `Bearer ${creds.apiKey}`, "Content-Type": "application/json", "X-Runway-Version": "2024-11-06" } }
68
+ );
69
+ const jobId = resp.data.id;
70
+ return `Video generation started. Job ID: ${jobId}. Use check_status with this ID to get the video URL (usually ready in 30–90 seconds).`;
71
+ }
72
+ if (action === "check_status") {
73
+ const resp = await axios.get(
74
+ `https://api.dev.runwayml.com/v1/tasks/${args.job_id}`,
75
+ { headers: { Authorization: `Bearer ${creds.apiKey}`, "X-Runway-Version": "2024-11-06" } }
76
+ );
77
+ const task = resp.data;
78
+ const status = task.status;
79
+ if (status === "SUCCEEDED") return `Video ready: ${task.output?.[0] || "URL not available"}`;
80
+ if (status === "FAILED") return `Video generation failed: ${task.failure || "Unknown error"}`;
81
+ return `Status: ${status}. Progress: ${task.progress || 0}%. Check again in a few seconds.`;
82
+ }
83
+ }
84
+
85
+ // ── Kling (Kuaishou) ──────────────────────────────────────────────────────
86
+ if (type === "kling") {
87
+ if (action === "generate_video") {
88
+ const endpoint = args.image_url
89
+ ? "https://api.klingai.com/v1/videos/image2video"
90
+ : "https://api.klingai.com/v1/videos/text2video";
91
+ const body = {
92
+ model_name: "kling-v1",
93
+ prompt: args.prompt,
94
+ duration: String(args.duration || 5),
95
+ aspect_ratio: args.ratio || "16:9",
96
+ };
97
+ if (args.image_url) body.image = args.image_url;
98
+ const resp = await axios.post(endpoint, body, {
99
+ headers: { Authorization: `Bearer ${creds.apiKey}`, "Content-Type": "application/json" },
100
+ });
101
+ const jobId = resp.data.data?.task_id;
102
+ return `Video generation started. Task ID: ${jobId}. Use check_status to poll for completion.`;
103
+ }
104
+ if (action === "check_status") {
105
+ const resp = await axios.get(
106
+ `https://api.klingai.com/v1/videos/text2video/${args.job_id}`,
107
+ { headers: { Authorization: `Bearer ${creds.apiKey}` } }
108
+ );
109
+ const task = resp.data.data;
110
+ const status = task?.task_status;
111
+ if (status === "succeed") return `Video ready: ${task.task_result?.videos?.[0]?.url || "URL not available"}`;
112
+ if (status === "failed") return `Video generation failed: ${task.task_status_msg || "Unknown error"}`;
113
+ return `Status: ${status}. Check again in a few seconds.`;
114
+ }
115
+ }
116
+
117
+ // ── Pika ──────────────────────────────────────────────────────────────────
118
+ if (type === "pika") {
119
+ if (action === "generate_video") {
120
+ const resp = await axios.post(
121
+ "https://api.pika.art/v1/generate",
122
+ {
123
+ promptText: args.prompt,
124
+ image: args.image_url || null,
125
+ options: {
126
+ duration: args.duration || 3,
127
+ aspectRatio: args.ratio || "16:9",
128
+ frameRate: 24,
129
+ },
130
+ },
131
+ { headers: { Authorization: `Bearer ${creds.apiKey}`, "Content-Type": "application/json" } }
132
+ );
133
+ const jobId = resp.data.id || resp.data.jobId;
134
+ return `Video generation started. Job ID: ${jobId}. Use check_status to poll for completion.`;
135
+ }
136
+ if (action === "check_status") {
137
+ const resp = await axios.get(
138
+ `https://api.pika.art/v1/jobs/${args.job_id}`,
139
+ { headers: { Authorization: `Bearer ${creds.apiKey}` } }
140
+ );
141
+ const job = resp.data;
142
+ const status = job.status;
143
+ if (status === "finished" || status === "completed") return `Video ready: ${job.videos?.[0]?.url || job.url || "URL not available"}`;
144
+ if (status === "failed") return `Video generation failed: ${job.error || "Unknown error"}`;
145
+ return `Status: ${status}. Check again in a few seconds.`;
146
+ }
147
+ }
148
+
149
+ return `Unsupported action "${action}" for video connector type: ${type}`;
150
+ }
151
+
152
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool };
@@ -0,0 +1,111 @@
1
+ const axios = require("axios");
2
+
3
+ let _id = 1;
4
+ function rpc(url, method, params = []) {
5
+ return axios.post(url, { jsonrpc: "2.0", id: _id++, method, params }, { timeout: 30000 });
6
+ }
7
+
8
+ function getConnectorConfig(connector) {
9
+ const cfg = connector.config ? JSON.parse(connector.config) : {};
10
+ const auth = connector.authConfig ? JSON.parse(connector.authConfig) : {};
11
+ return { ...auth, ...cfg };
12
+ }
13
+
14
+ function getToolDefinitions(connector) {
15
+ const cfg = getConnectorConfig(connector);
16
+ const defaultAddress = cfg.walletAddress || null;
17
+ const addrHint = defaultAddress
18
+ ? ` Defaults to the configured wallet address (${defaultAddress}) if omitted.`
19
+ : "";
20
+
21
+ return [
22
+ {
23
+ type: "function",
24
+ function: {
25
+ name: `conn_${connector.id}_call`,
26
+ description: `Call a JSON-RPC / Web3 method on "${connector.name}" (Ethereum, Solana, or any JSON-RPC node).`,
27
+ parameters: {
28
+ type: "object",
29
+ properties: {
30
+ method: { type: "string", description: "RPC method name, e.g. eth_blockNumber, eth_getBalance, solana getSlot" },
31
+ params: { type: "array", description: "Array of parameters for the method", items: {} },
32
+ },
33
+ required: ["method"],
34
+ },
35
+ },
36
+ },
37
+ {
38
+ type: "function",
39
+ function: {
40
+ name: `conn_${connector.id}_get_balance`,
41
+ description: `Get the native token balance of an address on "${connector.name}".${addrHint}`,
42
+ parameters: {
43
+ type: "object",
44
+ properties: {
45
+ address: { type: "string", description: `Wallet address.${addrHint}` },
46
+ },
47
+ required: [],
48
+ },
49
+ },
50
+ },
51
+ {
52
+ type: "function",
53
+ function: {
54
+ name: `conn_${connector.id}_get_block`,
55
+ description: `Get block information from "${connector.name}".`,
56
+ parameters: {
57
+ type: "object",
58
+ properties: {
59
+ block: { type: "string", description: "Block number (hex) or 'latest'" },
60
+ },
61
+ required: [],
62
+ },
63
+ },
64
+ },
65
+ ];
66
+ }
67
+
68
+ function getAnthropicToolDefinitions(connector) {
69
+ return getToolDefinitions(connector).map(t => ({
70
+ name: t.function.name,
71
+ description: t.function.description,
72
+ input_schema: t.function.parameters,
73
+ }));
74
+ }
75
+
76
+ async function executeTool(action, args, connector) {
77
+ const cfg = getConnectorConfig(connector);
78
+
79
+ const rpcUrl = cfg.baseUrl || cfg.rpcUrl || "";
80
+ const defaultWallet = cfg.walletAddress || null;
81
+
82
+ if (!rpcUrl) return "Web3 RPC URL (baseUrl) not configured.";
83
+
84
+ try {
85
+ if (action === "call") {
86
+ const res = await rpc(rpcUrl, args.method, args.params || []);
87
+ if (res.data.error) return `RPC error: ${JSON.stringify(res.data.error)}`;
88
+ return JSON.stringify(res.data.result, null, 2);
89
+ }
90
+ if (action === "get_balance") {
91
+ const address = args.address || defaultWallet;
92
+ if (!address) return "No wallet address provided and none configured in the connector (walletAddress).";
93
+ const res = await rpc(rpcUrl, "eth_getBalance", [address, "latest"]);
94
+ if (res.data.error) return `RPC error: ${JSON.stringify(res.data.error)}`;
95
+ const hex = res.data.result;
96
+ const wei = BigInt(hex);
97
+ const eth = Number(wei) / 1e18;
98
+ return `Balance of ${address}: ${hex} wei (${eth.toFixed(6)} ETH)`;
99
+ }
100
+ if (action === "get_block") {
101
+ const res = await rpc(rpcUrl, "eth_getBlockByNumber", [args.block || "latest", false]);
102
+ if (res.data.error) return `RPC error: ${JSON.stringify(res.data.error)}`;
103
+ return JSON.stringify(res.data.result, null, 2).slice(0, 4000);
104
+ }
105
+ return `Unknown action: ${action}`;
106
+ } catch (err) {
107
+ return `Web3 error: ${err.message}`;
108
+ }
109
+ }
110
+
111
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool };
@@ -0,0 +1,82 @@
1
+ const axios = require("axios");
2
+
3
+ function cfg(connector) {
4
+ return connector.authConfig ? JSON.parse(connector.authConfig) : {};
5
+ }
6
+
7
+ function client({ subdomain, email, apiToken }) {
8
+ return axios.create({
9
+ baseURL: `https://${subdomain}.zendesk.com/api/v2`,
10
+ auth: { username: `${email}/token`, password: apiToken },
11
+ headers: { "Content-Type": "application/json" },
12
+ });
13
+ }
14
+
15
+ const TOOLS = c => [
16
+ { action: "search_tickets", desc: `Search Zendesk tickets via ${c.name}.`,
17
+ params: { query: { type: "string", description: "Search query (e.g. status:open priority:high)." } }, required: ["query"] },
18
+ { action: "get_ticket", desc: `Get a Zendesk ticket by ID via ${c.name}.`,
19
+ params: { ticketId: { type: "number", description: "Ticket ID." } }, required: ["ticketId"] },
20
+ { action: "create_ticket", desc: `Create a Zendesk ticket via ${c.name}.`,
21
+ params: { subject: { type: "string", description: "Ticket subject." },
22
+ comment: { type: "string", description: "Initial comment / description." },
23
+ requesterEmail: { type: "string", description: "Requester email address." },
24
+ priority: { type: "string", description: "Priority: low, normal, high, urgent." } }, required: ["subject","comment"] },
25
+ ];
26
+
27
+ function getToolDefinitions(connector) {
28
+ return TOOLS(connector).map(t => ({
29
+ type: "function",
30
+ function: { name: `conn_${connector.id}_${t.action}`, description: t.desc,
31
+ parameters: { type: "object", properties: t.params, required: t.required } },
32
+ }));
33
+ }
34
+
35
+ function getAnthropicToolDefinitions(connector) {
36
+ return TOOLS(connector).map(t => ({
37
+ name: `conn_${connector.id}_${t.action}`, description: t.desc,
38
+ input_schema: { type: "object", properties: t.params, required: t.required },
39
+ }));
40
+ }
41
+
42
+ async function executeTool(action, args, connector) {
43
+ const creds = cfg(connector);
44
+ if (!creds.subdomain || !creds.apiToken) return "Zendesk not configured. Please add credentials in Integrations.";
45
+ const api = client(creds);
46
+
47
+ try {
48
+ if (action === "search_tickets") {
49
+ const res = await api.get(`/search.json?query=type:ticket ${encodeURIComponent(args.query)}&per_page=15`);
50
+ const results = res.data.results || [];
51
+ if (!results.length) return "No tickets found.";
52
+ return results.map(t => `[#${t.id}] ${t.subject} — Status: ${t.status}, Priority: ${t.priority}`).join("\n");
53
+ }
54
+
55
+ if (action === "get_ticket") {
56
+ const res = await api.get(`/tickets/${args.ticketId}.json`);
57
+ const t = res.data.ticket;
58
+ return JSON.stringify({ id: t.id, subject: t.subject, status: t.status, priority: t.priority, created: t.created_at, description: t.description?.slice(0, 500) }, null, 2);
59
+ }
60
+
61
+ if (action === "create_ticket") {
62
+ const { subject, comment, requesterEmail, priority = "normal" } = args;
63
+ const body = { ticket: { subject, comment: { body: comment }, priority } };
64
+ if (requesterEmail) body.ticket.requester = { email: requesterEmail };
65
+ const res = await api.post("/tickets.json", body);
66
+ return `Created ticket #${res.data.ticket.id}: ${subject}`;
67
+ }
68
+
69
+ return `Unknown Zendesk action: ${action}`;
70
+ } catch (err) {
71
+ return `Zendesk error: ${err.response?.data?.description || err.message}`;
72
+ }
73
+ }
74
+
75
+ async function testConnection(authConfig) {
76
+ try {
77
+ const res = await client(authConfig).get("/tickets.json?per_page=1");
78
+ return Array.isArray(res.data.tickets);
79
+ } catch { return false; }
80
+ }
81
+
82
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool, testConnection };
@@ -0,0 +1,246 @@
1
+ const nodemailer = require("nodemailer");
2
+ const { ImapFlow } = require("imapflow");
3
+
4
+ function getTransport(auth) {
5
+ const port = parseInt(auth.smtpPort || "465");
6
+ return nodemailer.createTransport({
7
+ host: auth.smtpHost || "smtppro.zoho.in",
8
+ port,
9
+ secure: port === 465,
10
+ auth: {
11
+ user: auth.email,
12
+ pass: auth.appPassword,
13
+ },
14
+ });
15
+ }
16
+
17
+ function getImapClient(auth) {
18
+ return new ImapFlow({
19
+ host: auth.imapHost || "imappro.zoho.in",
20
+ port: parseInt(auth.imapPort || "993"),
21
+ secure: true,
22
+ auth: {
23
+ user: auth.email,
24
+ pass: auth.appPassword,
25
+ },
26
+ logger: false,
27
+ });
28
+ }
29
+
30
+ function getToolDefinitions(connector) {
31
+ return [
32
+ {
33
+ type: "function",
34
+ function: {
35
+ name: `conn_${connector.id}_send_email`,
36
+ description: `Send an outbound email via Zoho Mail (${connector.name}). Use for sales outreach, follow-ups, or notifications.`,
37
+ parameters: {
38
+ type: "object",
39
+ properties: {
40
+ to: { type: "string", description: "Recipient email address (or comma-separated list)." },
41
+ subject: { type: "string", description: "Email subject line." },
42
+ body: { type: "string", description: "Email body (plain text or HTML)." },
43
+ cc: { type: "string", description: "CC email addresses (optional, comma-separated)." },
44
+ replyTo: { type: "string", description: "Reply-to email address (optional)." },
45
+ },
46
+ required: ["to", "subject", "body"]
47
+ }
48
+ }
49
+ },
50
+ {
51
+ type: "function",
52
+ function: {
53
+ name: `conn_${connector.id}_search_emails`,
54
+ description: `Search emails in a Zoho Mail inbox folder. Use to find replies, bounces, or messages from specific senders.`,
55
+ parameters: {
56
+ type: "object",
57
+ properties: {
58
+ folder: { type: "string", description: "Mailbox folder to search. Default: INBOX." },
59
+ from: { type: "string", description: "Filter by sender email address (optional)." },
60
+ subject: { type: "string", description: "Filter by subject keyword (optional)." },
61
+ since_days: { type: "number", description: "Only return emails from the last N days. Default: 7." },
62
+ limit: { type: "number", description: "Max emails to return. Default: 50." },
63
+ },
64
+ required: []
65
+ }
66
+ }
67
+ },
68
+ {
69
+ type: "function",
70
+ function: {
71
+ name: `conn_${connector.id}_read_email`,
72
+ description: `Read the full body of a specific email by its UID.`,
73
+ parameters: {
74
+ type: "object",
75
+ properties: {
76
+ uid: { type: "string", description: "Email UID returned from search_emails." },
77
+ folder: { type: "string", description: "Mailbox folder. Default: INBOX." },
78
+ },
79
+ required: ["uid"]
80
+ }
81
+ }
82
+ }
83
+ ];
84
+ }
85
+
86
+ function getAnthropicToolDefinitions(connector) {
87
+ return [
88
+ {
89
+ name: `conn_${connector.id}_send_email`,
90
+ description: `Send an outbound email via Zoho Mail (${connector.name}). Use for sales outreach, follow-ups, or notifications.`,
91
+ input_schema: {
92
+ type: "object",
93
+ properties: {
94
+ to: { type: "string", description: "Recipient email address (or comma-separated list)." },
95
+ subject: { type: "string", description: "Email subject line." },
96
+ body: { type: "string", description: "Email body (plain text or HTML)." },
97
+ cc: { type: "string", description: "CC email addresses (optional, comma-separated)." },
98
+ replyTo: { type: "string", description: "Reply-to email address (optional)." },
99
+ },
100
+ required: ["to", "subject", "body"]
101
+ }
102
+ },
103
+ {
104
+ name: `conn_${connector.id}_search_emails`,
105
+ description: `Search emails in a Zoho Mail inbox folder. Use to find replies, bounces, or messages from specific senders.`,
106
+ input_schema: {
107
+ type: "object",
108
+ properties: {
109
+ folder: { type: "string", description: "Mailbox folder to search. Default: INBOX." },
110
+ from: { type: "string", description: "Filter by sender email address (optional)." },
111
+ subject: { type: "string", description: "Filter by subject keyword (optional)." },
112
+ since_days: { type: "number", description: "Only return emails from the last N days. Default: 7." },
113
+ limit: { type: "number", description: "Max emails to return. Default: 50." },
114
+ },
115
+ required: []
116
+ }
117
+ },
118
+ {
119
+ name: `conn_${connector.id}_read_email`,
120
+ description: `Read the full body of a specific email by its UID.`,
121
+ input_schema: {
122
+ type: "object",
123
+ properties: {
124
+ uid: { type: "string", description: "Email UID returned from search_emails." },
125
+ folder: { type: "string", description: "Mailbox folder. Default: INBOX." },
126
+ },
127
+ required: ["uid"]
128
+ }
129
+ }
130
+ ];
131
+ }
132
+
133
+ async function executeTool(action, args, connector) {
134
+ const auth = connector.authConfig ? JSON.parse(connector.authConfig) : {};
135
+
136
+ if (!auth.email || !auth.appPassword) {
137
+ return "Zoho Mail not configured. Please add your email and app password in the connector settings.";
138
+ }
139
+
140
+ if (action === "send_email") {
141
+ const { to, subject, body, cc, replyTo } = args;
142
+ if (!to || !subject || !body) return "Missing required fields: to, subject, body.";
143
+
144
+ const isHtml = /<[a-z][\s\S]*>/i.test(body);
145
+ const mail = {
146
+ from: auth.fromName ? `${auth.fromName} <${auth.email}>` : auth.email,
147
+ to,
148
+ subject,
149
+ ...(isHtml ? { html: body } : { text: body }),
150
+ ...(cc ? { cc } : {}),
151
+ ...(replyTo ? { replyTo } : {}),
152
+ };
153
+
154
+ try {
155
+ const transport = getTransport(auth);
156
+ await transport.sendMail(mail);
157
+ return `Email sent to ${to} with subject "${subject}".`;
158
+ } catch (err) {
159
+ return `Zoho Mail error: ${err.message}`;
160
+ }
161
+ }
162
+
163
+ if (action === "search_emails") {
164
+ const folder = args.folder || "INBOX";
165
+ const sinceDays = args.since_days || 7;
166
+ const limit = args.limit || 50;
167
+
168
+ const since = new Date();
169
+ since.setDate(since.getDate() - sinceDays);
170
+
171
+ const client = getImapClient(auth);
172
+ try {
173
+ await client.connect();
174
+ await client.mailboxOpen(folder);
175
+
176
+ const searchCriteria = { since };
177
+ if (args.from) searchCriteria.from = args.from;
178
+ if (args.subject) searchCriteria.subject = args.subject;
179
+
180
+ const messages = [];
181
+ for await (const msg of client.fetch(searchCriteria, {
182
+ uid: true, envelope: true, bodyStructure: true, size: true
183
+ })) {
184
+ messages.push({
185
+ uid: String(msg.uid),
186
+ from: msg.envelope.from?.[0]?.address || "",
187
+ fromName:msg.envelope.from?.[0]?.name || "",
188
+ subject: msg.envelope.subject || "(no subject)",
189
+ date: msg.envelope.date?.toISOString() || "",
190
+ size: msg.size,
191
+ });
192
+ if (messages.length >= limit) break;
193
+ }
194
+
195
+ await client.logout();
196
+ if (messages.length === 0) return `No emails found in ${folder} matching the criteria.`;
197
+ return JSON.stringify(messages, null, 2);
198
+ } catch (err) {
199
+ try { await client.logout(); } catch {}
200
+ return `IMAP search error: ${err.message}`;
201
+ }
202
+ }
203
+
204
+ if (action === "read_email") {
205
+ const folder = args.folder || "INBOX";
206
+ const uid = args.uid;
207
+ if (!uid) return "Missing required field: uid.";
208
+
209
+ const client = getImapClient(auth);
210
+ try {
211
+ await client.connect();
212
+ await client.mailboxOpen(folder);
213
+
214
+ let result = null;
215
+ for await (const msg of client.fetch({ uid: parseInt(uid) }, {
216
+ uid: true, envelope: true, bodyParts: ["TEXT", "1", "1.1", "2"]
217
+ })) {
218
+ const parts = msg.bodyParts;
219
+ let body = "";
220
+ for (const [, content] of parts) {
221
+ if (content) body += content.toString("utf8");
222
+ }
223
+ result = {
224
+ uid: String(msg.uid),
225
+ from: msg.envelope.from?.[0]?.address || "",
226
+ fromName:msg.envelope.from?.[0]?.name || "",
227
+ subject: msg.envelope.subject || "(no subject)",
228
+ date: msg.envelope.date?.toISOString() || "",
229
+ body: body.slice(0, 3000),
230
+ };
231
+ break;
232
+ }
233
+
234
+ await client.logout();
235
+ if (!result) return `Email with UID ${uid} not found.`;
236
+ return JSON.stringify(result, null, 2);
237
+ } catch (err) {
238
+ try { await client.logout(); } catch {}
239
+ return `IMAP read error: ${err.message}`;
240
+ }
241
+ }
242
+
243
+ return `Unknown Zoho Mail action: ${action}`;
244
+ }
245
+
246
+ module.exports = { getToolDefinitions, getAnthropicToolDefinitions, executeTool };