Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion apps/server/src/routes/ai.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,67 @@
import { getDriverFromConnectionId, ZeroMCP } from '../services/mcp-service/mcp';
import { CallService } from '../services/call-service/call-service';
import { ZeroMCP } from '../services/mcp-service/mcp';
import { composeEmail } from '../trpc/routes/ai/compose';
import { env } from 'cloudflare:workers';
import { tools } from './agent/tools';
import { Tools } from '../types';
import twilio from 'twilio';
import { Hono } from 'hono';

export const aiRouter = new Hono();

aiRouter.get('/', (c) => c.text('Twilio + ElevenLabs + AI Phone System Ready'));

aiRouter.post('/do/:action', async (c) => {
const action = c.req.param('action') as Tools;
const body = await c.req.json();
console.log('[DEBUG] action', action, body);
Comment on lines +15 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation for request body.

The request body is parsed without validation, which could lead to runtime errors if required fields are missing.

Consider using a schema validation library like Zod to validate the request body based on the action type:

const bodySchema = z.object({
  prompt: z.string().optional(),
  emailSubject: z.string().optional(),
  to: z.array(z.object({
    name: z.string().optional(),
    email: z.string().email(),
  })).optional(),
  subject: z.string().optional(),
  message: z.string().optional(),
});

const body = bodySchema.parse(await c.req.json());
🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts around lines 15 to 17, the request body is
parsed without validation, which risks runtime errors if required fields are
missing. To fix this, define a Zod schema that matches the expected structure of
the request body, including optional and required fields as appropriate. Then,
replace the direct JSON parsing with a call to the schema's parse method to
validate and parse the input safely before using it.

const connectionId = c.req.header('X-Connection-Id');
if (!connectionId) {
return new Response('Unauthorized', { status: 401 });
}
try {
const driver = await getDriverFromConnectionId(connectionId);
switch (action) {
case Tools.ListThreads:
const threads = await Promise.all(
(await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) =>
driver.get(thread.id).then((thread) => ({
id: thread.latest?.id,
subject: thread.latest?.subject,
sender: thread.latest?.sender,
date: thread.latest?.receivedOn,
})),
),
);
return c.json({ success: true, result: threads });
case Tools.ComposeEmail:
const newBody = await composeEmail({
prompt: body.prompt,
emailSubject: body.emailSubject,
username: 'Nizar Abi Zaher',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove hard-coded username.

The username 'Nizar Abi Zaher' is hard-coded and should be retrieved from the user's connection or profile data.

Consider retrieving the username from the database using the connection ID or from user authentication context.

🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts at line 41, replace the hard-coded username
'Nizar Abi Zaher' with a dynamic value retrieved from the user's connection or
authentication context. Modify the code to access the current user's profile or
session data to get the username, ensuring it reflects the actual connected user
instead of a fixed string.

connectionId,
});
return c.json({ success: true, result: newBody });
case Tools.SendEmail:
const result = await driver.create({
to: body.to.map((to: any) => ({
name: to.name ?? to.email,
email: to.email ?? 'founders@0.email',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove hard-coded fallback email address.

The fallback email 'founders@0.email' should not be hard-coded. This could lead to emails being sent to unintended recipients.

Either require a valid email or throw an error if none is provided:

-            email: to.email ?? 'founders@0.email',
+            email: to.email,

Add validation to ensure all recipients have valid email addresses before processing.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
email: to.email ?? 'founders@0.email',
email: to.email,
🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts at line 49, remove the hard-coded fallback email
'founders@0.email'. Instead, add validation to check if to.email is present and
valid; if not, throw an error to prevent processing with an invalid or missing
email. This ensures no unintended recipients receive emails and enforces proper
input validation.

})),
subject: body.subject,
message: body.message,
attachments: [],
headers: {},
});
return c.json({ success: true, result });
default:
return c.json({ success: false, error: 'Not implemented' }, 400);
}
Comment on lines +24 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix switch case variable declarations to prevent scope bleed.

The static analysis correctly identifies that variable declarations in switch cases can be accessed by other cases, which is a potential source of bugs.

Wrap each case in a block to scope the variables properly:

-    switch (action) {
-      case Tools.ListThreads:
-        const threads = await Promise.all(
+    switch (action) {
+      case Tools.ListThreads: {
+        const threads = await Promise.all(
           (await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) =>
             driver.get(thread.id).then((thread) => ({
               id: thread.latest?.id,
               subject: thread.latest?.subject,
               sender: thread.latest?.sender,
               date: thread.latest?.receivedOn,
             })),
           ),
         );
         return c.json({ success: true, result: threads });
-      case Tools.ComposeEmail:
-        const newBody = await composeEmail({
+      }
+      case Tools.ComposeEmail: {
+        const newBody = await composeEmail({
           prompt: body.prompt,
           emailSubject: body.emailSubject,
           username: 'Nizar Abi Zaher',
           connectionId,
         });
         return c.json({ success: true, result: newBody });
-      case Tools.SendEmail:
-        const result = await driver.create({
+      }
+      case Tools.SendEmail: {
+        const result = await driver.create({
           to: body.to.map((to: any) => ({
             name: to.name ?? to.email,
             email: to.email ?? 'founders@0.email',
           })),
           subject: body.subject,
           message: body.message,
           attachments: [],
           headers: {},
         });
         return c.json({ success: true, result });
+      }
       default:
         return c.json({ success: false, error: 'Not implemented' }, 400);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch (action) {
case Tools.ListThreads:
const threads = await Promise.all(
(await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) =>
driver.get(thread.id).then((thread) => ({
id: thread.latest?.id,
subject: thread.latest?.subject,
sender: thread.latest?.sender,
date: thread.latest?.receivedOn,
})),
),
);
return c.json({ success: true, result: threads });
case Tools.ComposeEmail:
const newBody = await composeEmail({
prompt: body.prompt,
emailSubject: body.emailSubject,
username: 'Nizar Abi Zaher',
connectionId,
});
return c.json({ success: true, result: newBody });
case Tools.SendEmail:
const result = await driver.create({
to: body.to.map((to: any) => ({
name: to.name ?? to.email,
email: to.email ?? 'founders@0.email',
})),
subject: body.subject,
message: body.message,
attachments: [],
headers: {},
});
return c.json({ success: true, result });
default:
return c.json({ success: false, error: 'Not implemented' }, 400);
}
switch (action) {
case Tools.ListThreads: {
const threads = await Promise.all(
(await driver.list({ folder: 'inbox', maxResults: 5 })).threads.map((thread) =>
driver.get(thread.id).then((thread) => ({
id: thread.latest?.id,
subject: thread.latest?.subject,
sender: thread.latest?.sender,
date: thread.latest?.receivedOn,
})),
),
);
return c.json({ success: true, result: threads });
}
case Tools.ComposeEmail: {
const newBody = await composeEmail({
prompt: body.prompt,
emailSubject: body.emailSubject,
username: 'Nizar Abi Zaher',
connectionId,
});
return c.json({ success: true, result: newBody });
}
case Tools.SendEmail: {
const result = await driver.create({
to: body.to.map((to: any) => ({
name: to.name ?? to.email,
email: to.email ?? 'founders@0.email',
})),
subject: body.subject,
message: body.message,
attachments: [],
headers: {},
});
return c.json({ success: true, result });
}
default:
return c.json({ success: false, error: 'Not implemented' }, 400);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-35: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 38-43: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 46-55: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In apps/server/src/routes/ai.ts between lines 24 and 59, the variables declared
inside each switch case are not scoped properly, which can cause them to be
accessible in other cases and lead to bugs. To fix this, wrap the code inside
each case block with curly braces {} to create a new block scope, ensuring
variables like threads, newBody, and result are confined to their respective
cases.

} catch (error: any) {
return c.json({ success: false, error: error.message }, 400);
}
});

aiRouter.mount(
'/mcp',
async (request, env, ctx) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/services/mcp-service/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { createDb } from '../../db';
import { generateText } from 'ai';
import { z } from 'zod';

const getDriverFromConnectionId = async (connectionId: string) => {
export const getDriverFromConnectionId = async (connectionId: string) => {
const db = createDb(env.HYPERDRIVE.connectionString);
const activeConnection = await db.query.connection.findFirst({
where: (connection, ops) => ops.eq(connection.id, connectionId),
Expand Down
1 change: 1 addition & 0 deletions apps/server/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"VITE_PUBLIC_BACKEND_URL": "http://localhost:8787",
"VITE_PUBLIC_APP_URL": "http://localhost:3000",
"JWT_SECRET": "secret",
"ELEVENLABS_API_KEY": "1234567890",
},
"kv_namespaces": [
{
Expand Down