From 4488ec92c35d7e5f9ea7995e8626df17bd6e82e2 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Sat, 19 Apr 2025 01:41:53 -0400 Subject: [PATCH 1/2] added microsoft --- app/api/chat/route.ts | 85 ++++++++ app/api/oauth/connections/route.ts | 71 +++--- app/connections/page.tsx | 6 + app/page.tsx | 54 +++++ components/prompt-trigger.tsx | 3 +- components/user-menu.tsx | 6 + hooks/use-connection-notification.tsx | 20 ++ lib/connection-manager.ts | 6 +- lib/descope.ts | 10 + lib/oauth-utils.ts | 7 + lib/tools/base.ts | 5 +- lib/tools/index.ts | 1 + lib/tools/microsoft-teams.ts | 298 ++++++++++++++++++++++++++ public/logos/microsoft-teams-logo.png | Bin 0 -> 33486 bytes 14 files changed, 540 insertions(+), 32 deletions(-) create mode 100644 lib/tools/microsoft-teams.ts create mode 100644 public/logos/microsoft-teams-logo.png diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 06557f2..c500c1c 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -279,6 +279,7 @@ export async function POST(request: Request) { const crmContactsTool = toolRegistry.getTool("crm-contacts"); const googleMeetTool = toolRegistry.getTool("google-meet"); const slackTool = toolRegistry.getTool("slack"); + const teamsChatTool = toolRegistry.getTool("microsoft-teams"); // Define the tools object const toolsObject: any = { @@ -907,6 +908,90 @@ export async function POST(request: Request) { } }, }, + // Add Microsoft Teams meeting tool + createTeamsChat: { + description: + "Create a Microsoft Teams chat group and send an initial message", + parameters: z.object({ + title: z.string().describe("Chat group title"), + description: z.string().describe("Initial message content"), + startTime: z + .string() + .describe( + "Time reference in ISO format (if scheduling discussion)" + ), + duration: z + .number() + .describe("Duration in minutes (if scheduling discussion)"), + attendees: z + .array(z.string()) + .describe( + "List of attendee email addresses to include in the chat" + ), + timeZone: z + .string() + .optional() + .describe("Time zone (optional, defaults to user's timezone)"), + settings: z + .object({ + chatType: z + .enum(["group", "oneOnOne", "meeting"]) + .optional() + .describe("Type of chat to create"), + allowNewTimeProposals: z.boolean().optional(), + }) + .optional(), + }), + execute: async (data: any) => { + try { + if (!teamsChatTool) { + return { + success: false, + error: "Microsoft Teams tool not available", + message: + "Unable to create Teams chats. Please connect your Microsoft account.", + ui: { + type: "connection_required", + service: "microsoft-teams", + message: + "Please connect your Microsoft account to create Teams chats", + connectButton: { + text: "Connect Microsoft Teams", + action: "connection://microsoft-teams", + }, + }, + }; + } + + // Use the client's timezone if no timeZone was specified + if (!data.timeZone && timezone !== "UTC") { + data.timeZone = timezone; + } + + const result = await teamsChatTool.execute(userId, { + title: data.title, + description: data.description, + startTime: data.startTime, + duration: data.duration, + attendees: data.attendees, + timeZone: data.timeZone, + settings: data.settings, + }); + + return result; + } catch (error) { + return { + success: false, + error: + error instanceof Error + ? error.message + : "Unknown error creating Teams chat", + message: + "There was an error creating your Teams chat. Please try again later.", + }; + } + }, + }, }; // Add calendar tool if available diff --git a/app/api/oauth/connections/route.ts b/app/api/oauth/connections/route.ts index a2a129e..4549f86 100644 --- a/app/api/oauth/connections/route.ts +++ b/app/api/oauth/connections/route.ts @@ -5,6 +5,7 @@ import { getGoogleMeetToken, getCRMToken, getSlackToken, + getMicrosoftTeamsToken, } from "@/lib/descope"; import { trackOAuthEvent, trackError } from "@/lib/analytics"; import { getChatById, getChatMessages } from "@/lib/db/queries"; @@ -42,35 +43,45 @@ export async function GET() { // Get the status of all OAuth connections using default operations from lib/descope.ts console.log("Starting OAuth provider token fetch"); - const [googleCalendar, googleDocs, googleMeet, customCrm, slack] = - await Promise.all([ - getGoogleCalendarToken(userId).catch((e) => { - console.error("Error fetching Google Calendar token:", e); - console.error("Google Calendar error details:", { - name: e.name, - message: e.message, - status: e.status, - stack: e.stack, - }); - return { error: e.message, connected: false, originalError: e }; - }), - getGoogleDocsToken(userId).catch((e) => { - console.error("Error fetching Google Docs token:", e); - return { error: e.message, connected: false }; - }), - getGoogleMeetToken(userId).catch((e) => { - console.error("Error fetching Google Meet token:", e); - return { error: e.message, connected: false }; - }), - getCRMToken(userId).catch((e) => { - console.error("Error fetching CRM token:", e); - return { error: e.message, connected: false }; - }), - getSlackToken(userId).catch((e) => { - console.error("Error fetching Slack token:", e); - return { error: e.message, connected: false }; - }), - ]); + const [ + googleCalendar, + googleDocs, + googleMeet, + customCrm, + slack, + microsoftTeams, + ] = await Promise.all([ + getGoogleCalendarToken(userId).catch((e) => { + console.error("Error fetching Google Calendar token:", e); + console.error("Google Calendar error details:", { + name: e.name, + message: e.message, + status: e.status, + stack: e.stack, + }); + return { error: e.message, connected: false, originalError: e }; + }), + getGoogleDocsToken(userId).catch((e) => { + console.error("Error fetching Google Docs token:", e); + return { error: e.message, connected: false }; + }), + getGoogleMeetToken(userId).catch((e) => { + console.error("Error fetching Google Meet token:", e); + return { error: e.message, connected: false }; + }), + getCRMToken(userId).catch((e) => { + console.error("Error fetching CRM token:", e); + return { error: e.message, connected: false }; + }), + getSlackToken(userId).catch((e) => { + console.error("Error fetching Slack token:", e); + return { error: e.message, connected: false }; + }), + getMicrosoftTeamsToken(userId).catch((e) => { + console.error("Error fetching Microsoft Teams token:", e); + return { error: e.message, connected: false }; + }), + ]); // Process token responses const processConnection = (response: any) => { @@ -154,6 +165,7 @@ export async function GET() { "google-meet": processConnection(googleMeet), "custom-crm": processConnection(customCrm), slack: processConnection(slack), + "microsoft-teams": processConnection(microsoftTeams), }; trackOAuthEvent("connection_successful", { @@ -164,6 +176,7 @@ export async function GET() { googleMeetConnected: connections["google-meet"].connected, crmConnected: connections["custom-crm"].connected, slackConnected: connections["slack"].connected, + microsoftTeamsConnected: connections["microsoft-teams"].connected, }); return Response.json({ connections }); diff --git a/app/connections/page.tsx b/app/connections/page.tsx index d4aa89e..79cc831 100644 --- a/app/connections/page.tsx +++ b/app/connections/page.tsx @@ -84,6 +84,12 @@ export default function ConnectionsPage() { icon: "/logos/slack-logo.svg", connected: false, }, + { + id: "microsoft-teams", + name: "Microsoft Teams", + icon: "/logos/microsoft-teams-logo.png", + connected: false, + }, ]); // Function to fetch connections diff --git a/app/page.tsx b/app/page.tsx index 9e08324..4869b8e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -55,6 +55,7 @@ type PromptType = | "slack" | "summarize-deal" | "create-google-meet" + | "microsoft-teams" | "add-custom-tool"; interface PromptExplanation { @@ -277,6 +278,46 @@ const promptExplanations: Record = { ], apis: ["Google Calendar API"], }, + "microsoft-teams": { + title: "Microsoft Teams Chat", + description: + "Create Teams chat groups and send messages to collaborate with your team", + logo: "/logos/microsoft-teams-logo.png", + examples: [ + "Create a Teams chat with Sarah and Tom about the sales proposal", + "Start a Microsoft Teams discussion with the marketing team", + "Set up a Teams chat group for the product launch team", + "Create a Teams chat to discuss the client meeting tomorrow", + ], + steps: [ + { + title: "User Requests Teams Chat Creation", + description: + "The user asks to create a Microsoft Teams chat group with specific participants.", + }, + { + title: "Authentication Check", + description: + "The assistant verifies the user is authenticated and has connected Microsoft Teams via OAuth.", + }, + { + title: "Teams API Access", + description: + "Using the stored OAuth token from Descope, the assistant makes a secure API call to Microsoft Graph API.", + }, + { + title: "Chat Group Creation", + description: + "A new Teams chat group is created with the specified participants.", + }, + { + title: "Initial Message Sending", + description: + "The assistant sends an initial message to the chat group with the specified details or meeting information.", + }, + ], + apis: ["Microsoft Graph API"], + }, "add-custom-tool": { title: "Add Your Own Tool", description: @@ -1053,6 +1094,19 @@ export default function Home() { ) ), }, + { + id: "microsoft-teams", + title: "Microsoft Teams Chat", + description: "Create Teams chat groups with your colleagues", + logo: "/logos/microsoft-teams-logo.png", + action: () => + checkOAuthAndPrompt(() => + usePredefinedPrompt( + "Create a Teams chat with Sarah and Tom about the Q4 strategy", + "microsoft-teams" + ) + ), + }, { id: "add-custom-tool", title: "Add Your Own Tool", diff --git a/components/prompt-trigger.tsx b/components/prompt-trigger.tsx index cdc45db..c263330 100644 --- a/components/prompt-trigger.tsx +++ b/components/prompt-trigger.tsx @@ -8,13 +8,14 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { Calendar, FileText, Search, Video } from "lucide-react"; +import { Calendar, FileText, MessageSquare, Search, Video } from "lucide-react"; const icons = { "crm-lookup": Search, "schedule-meeting": Calendar, "create-google-meet": Video, "summarize-deal": FileText, + "microsoft-teams": MessageSquare, }; interface PromptTriggerProps { diff --git a/components/user-menu.tsx b/components/user-menu.tsx index 61e5adc..65ecb3e 100644 --- a/components/user-menu.tsx +++ b/components/user-menu.tsx @@ -70,6 +70,12 @@ export default function UserMenu({ onProfileClick }: UserMenuProps) { icon: "/logos/slack-logo.svg", connected: false, }, + { + id: "microsoft-teams", + name: "Microsoft Teams", + icon: "/logos/microsoft-teams-logo.png", + connected: false, + }, ]); const [isLoading, setIsLoading] = useState(false); const [isConnecting, setIsConnecting] = useState(null); diff --git a/hooks/use-connection-notification.tsx b/hooks/use-connection-notification.tsx index 3ecfb9b..3deb96f 100644 --- a/hooks/use-connection-notification.tsx +++ b/hooks/use-connection-notification.tsx @@ -70,6 +70,26 @@ const providers = { "team chat", ], }, + "microsoft-teams": { + id: "microsoft-teams", + name: "Microsoft Teams", + icon: "/logos/microsoft-teams-logo.png", + scopes: [ + "ChannelMember.ReadWrite.All", + "ChannelMessage.Send", + "Chat.ReadWrite.All", + "ChatMessage.Send", + "offline_access", + ], + keywords: [ + "teams", + "microsoft teams", + "teams meeting", + "video conference", + "teams call", + "online meeting", + ], + }, }; type ProviderKey = keyof typeof providers; diff --git a/lib/connection-manager.ts b/lib/connection-manager.ts index 473e9a5..0d9477c 100644 --- a/lib/connection-manager.ts +++ b/lib/connection-manager.ts @@ -7,7 +7,8 @@ export type OAuthProvider = | "google-docs" | "google-meet" | "custom-crm" - | "slack"; + | "slack" + | "microsoft-teams"; const CONNECTION_KEY_PREFIX = "oauth_connection_"; @@ -48,6 +49,7 @@ export function getAllConnectionStatuses(): Record { "google-meet": false, "custom-crm": false, slack: false, + "microsoft-teams": false, }; } @@ -57,6 +59,7 @@ export function getAllConnectionStatuses(): Record { "google-meet", "custom-crm", "slack", + "microsoft-teams", ]; return providers.reduce((statuses, provider) => { @@ -77,6 +80,7 @@ export function disconnectAll(): void { "google-meet", "custom-crm", "slack", + "microsoft-teams", ]; providers.forEach((provider) => { diff --git a/lib/descope.ts b/lib/descope.ts index 3fbd33c..0692ee4 100644 --- a/lib/descope.ts +++ b/lib/descope.ts @@ -300,3 +300,13 @@ export async function getSlackToken( withRefreshToken: false, }); } + +// Add Microsoft Teams token function +export async function getMicrosoftTeamsToken( + userId: string, + operation: string = "check_connection" +) { + return getOAuthToken(userId, "microsoft-teams", operation, { + withRefreshToken: false, + }); +} diff --git a/lib/oauth-utils.ts b/lib/oauth-utils.ts index c6a5ca7..62165e8 100644 --- a/lib/oauth-utils.ts +++ b/lib/oauth-utils.ts @@ -67,6 +67,13 @@ export const DEFAULT_SCOPES: Record = { ], "custom-crm": ["openid", "contacts:read", "deals:read"], slack: ["chat:write", "channels:manage", "users:read"], + "microsoft-teams": [ + "ChannelMember.ReadWrite.All", + "ChannelMessage.Send", + "Chat.ReadWrite.All", + "ChatMessage.Send", + "offline_access", + ], }; export async function getOAuthTokenWithScopeValidation( diff --git a/lib/tools/base.ts b/lib/tools/base.ts index f13a04d..fa41c10 100644 --- a/lib/tools/base.ts +++ b/lib/tools/base.ts @@ -365,7 +365,8 @@ export type OAuthProvider = | "google-meet" | "custom-crm" | "slack" - | "zoom"; + | "zoom" + | "microsoft-teams"; // Create standardized connection request for OAuth providers export function createConnectionRequest(options: { @@ -446,6 +447,8 @@ export function createConnectionRequest(options: { return "Slack"; case "zoom": return "Zoom"; + case "microsoft-teams": + return "Microsoft Teams"; default: return String(provider).replace(/-/g, " "); } diff --git a/lib/tools/index.ts b/lib/tools/index.ts index 7e26a90..7cbc26c 100644 --- a/lib/tools/index.ts +++ b/lib/tools/index.ts @@ -6,6 +6,7 @@ import "./calendar"; import "./calendar-list"; import "./google-meet"; import "./slack"; +import "./microsoft-teams"; // Export the tool registry for direct access export { toolRegistry } from "./base"; diff --git a/lib/tools/microsoft-teams.ts b/lib/tools/microsoft-teams.ts new file mode 100644 index 0000000..6ee2563 --- /dev/null +++ b/lib/tools/microsoft-teams.ts @@ -0,0 +1,298 @@ +import { + Tool, + ToolConfig, + ToolResponse, + toolRegistry, + createConnectionRequest, +} from "./base"; +import { getOAuthTokenWithScopeValidation } from "../oauth-utils"; +import { getCurrentDateContext } from "@/lib/date-utils"; + +export interface TeamsEvent { + title: string; + description?: string; + startTime: string; + duration: number; + attendees?: string[]; + timeZone?: string; + settings?: { + chatType?: "group" | "oneOnOne" | "meeting"; + allowNewTimeProposals?: boolean; + }; +} + +export class TeamsChatTool extends Tool { + config: ToolConfig = { + id: "microsoft-teams", + name: "Microsoft Teams", + description: "Create Microsoft Teams chats and send messages", + scopes: [ + "ChannelMember.ReadWrite.All", + "ChannelMessage.Send", + "Chat.ReadWrite.All", + "ChatMessage.Send", + "offline_access", + ], + requiredFields: ["title", "startTime", "duration"], + optionalFields: ["description", "attendees", "timeZone", "settings"], + capabilities: [ + "Create Microsoft Teams meetings", + "Generate Teams meeting links", + "Schedule video conferences", + "Manage meeting settings", + ], + oauthConfig: { + provider: "microsoft-teams", + defaultScopes: [ + "ChannelMember.ReadWrite.All", + "ChannelMessage.Send", + "Chat.ReadWrite.All", + "ChatMessage.Send", + "offline_access", + ], + }, + }; + + validate(data: TeamsEvent): ToolResponse | null { + // Get current date context for validation + const dateContext = getCurrentDateContext(); + + if (!data.title) { + return { + success: false, + error: "Missing title", + needsInput: { + field: "title", + message: "Please provide a meeting title", + }, + }; + } + + if (!data.startTime) { + return { + success: false, + error: "Missing start time", + needsInput: { + field: "startTime", + message: `Please provide a start time. Today is ${dateContext.currentDate}.`, + }, + }; + } + + if (!data.duration) { + return { + success: false, + error: "Missing duration", + needsInput: { + field: "duration", + message: "Please provide a meeting duration in minutes", + }, + }; + } + + return null; + } + + async execute(userId: string, data: TeamsEvent): Promise { + try { + // Include date context when processing + const dateContext = getCurrentDateContext(); + console.log("Creating Microsoft Teams meeting:", { + title: data.title, + startTime: data.startTime, + duration: data.duration, + currentDate: dateContext.currentDate, + }); + + // Get OAuth token for Microsoft Graph API + const tokenResponse = await getOAuthTokenWithScopeValidation( + userId, + "microsoft-teams", + { + appId: "microsoft-teams", + userId, + scopes: this.config.scopes, + operation: "tool_calling", + } + ); + + if (!tokenResponse || "error" in tokenResponse) { + // Extract scope information if available + const requiredScopes = + "requiredScopes" in tokenResponse + ? tokenResponse.requiredScopes + : this.config.scopes; + const currentScopes = + "currentScopes" in tokenResponse + ? tokenResponse.currentScopes + : undefined; + + // Use standardized connection request + return createConnectionRequest({ + provider: "microsoft-teams", + isReconnect: currentScopes && currentScopes.length > 0, + requiredScopes: requiredScopes, + currentScopes: currentScopes, + customMessage: + "Please connect your Microsoft account to create Teams meetings", + }); + } + + // Extract the access token + const accessToken = tokenResponse.token?.accessToken; + + // Calculate end time from duration + const startTime = new Date(data.startTime); + const endTime = new Date(startTime.getTime() + data.duration * 60000); + + // First, create a chat with all the attendees + const attendeesList = data.attendees || []; + + // Create chat members array for the API request + const chatMembers = attendeesList.map((email) => ({ + "@odata.type": "#microsoft.graph.aadUserConversationMember", + roles: ["owner"], + "user@odata.bind": `https://graph.microsoft.com/v1.0/users('${email}')`, + })); + + // Create the chat + const chatPayload = { + chatType: "group", + topic: data.title, + members: chatMembers, + }; + + // Create a chat using Microsoft Graph API + const chatResponse = await fetch( + "https://graph.microsoft.com/v1.0/chats", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(chatPayload), + } + ); + + if (!chatResponse.ok) { + const errorData = await chatResponse.json(); + console.error("Microsoft Graph API error creating chat:", errorData); + + // Check if this is a scope or permission error + if (chatResponse.status === 401 || chatResponse.status === 403) { + return createConnectionRequest({ + provider: "microsoft-teams", + isReconnect: true, + requiredScopes: this.config.scopes, + customMessage: + "You need additional permissions to create Teams chats. Please reconnect with the required scopes.", + }); + } + + throw new Error( + `Failed to create Teams chat: ${ + errorData.error?.message || chatResponse.statusText + }` + ); + } + + const chatData = await chatResponse.json(); + const chatId = chatData.id; + + // Now send a message to the chat with meeting details + const messageContent = ` +${data.title} +

${data.description || ""}

+

Start: ${startTime.toLocaleString()}

+

End: ${endTime.toLocaleString()}

+

Duration: ${data.duration} minutes

+`; + + // Send a message to the chat + const messagePayload = { + body: { + contentType: "html", + content: messageContent, + }, + }; + + const messageResponse = await fetch( + `https://graph.microsoft.com/v1.0/chats/${chatId}/messages`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(messagePayload), + } + ); + + if (!messageResponse.ok) { + const errorData = await messageResponse.json(); + console.error("Microsoft Graph API error sending message:", errorData); + + // We'll still continue since at least the chat was created + console.warn("Failed to send initial message to Teams chat"); + } + + // Get chat webUrl + const chatUrl = chatData.webUrl; + + return { + success: true, + data: { + chatId: chatId, + joinUrl: chatUrl, + formattedMessage: `Microsoft Teams chat "${data.title}" created successfully! Join here: [Join Teams Chat](${chatUrl})`, + meetingInfo: { + topic: data.title, + startTime: startTime.toISOString(), + duration: data.duration, + timezone: data.timeZone || "UTC", + joinUrl: chatUrl, + }, + }, + }; + } catch (error) { + console.error("Error creating Microsoft Teams meeting:", error); + + // Check if this is an insufficient permissions error + const isInsufficientPermissions = + error instanceof Error && + (error.message.includes("Insufficient Permission") || + error.message.includes("403") || + (error as any).code === 403); + + if (isInsufficientPermissions) { + // Use standardized connection request for reconnection + return createConnectionRequest({ + provider: "microsoft-teams", + isReconnect: true, + requiredScopes: this.config.scopes, + customMessage: + "You need additional permissions to create Teams chats. Please reconnect with the required scopes.", + }); + } + + // Default error response + return { + success: false, + status: "error", + error: + error instanceof Error + ? error.message + : "Failed to create Microsoft Teams chat", + ui: { + type: "error", + message: + "There was an error creating your Teams chat. Please try again later.", + }, + }; + } + } +} + +// Register the tool with the registry +toolRegistry.register(new TeamsChatTool()); diff --git a/public/logos/microsoft-teams-logo.png b/public/logos/microsoft-teams-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3cd76acba52cbf23859fc42c623c420004be6423 GIT binary patch literal 33486 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 literal 0 HcmV?d00001 From e2add1a98a032e99cae03b1d3b2a81c77a9f4052 Mon Sep 17 00:00:00 2001 From: Kevin J Gao <32936811+gaokevin1@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:13:40 -0400 Subject: [PATCH 2/2] added linkedin and removed teams --- app/api/chat/route.ts | 129 ++++++----- app/api/oauth/connections/route.ts | 71 +++--- app/connections/page.tsx | 6 +- app/page.tsx | 103 +++++---- components/chat.tsx | 7 + components/prompt-trigger.tsx | 3 +- components/user-menu.tsx | 6 +- hooks/use-connection-notification.tsx | 33 ++- lib/connection-manager.ts | 6 +- lib/descope.ts | 10 - lib/oauth-utils.ts | 8 +- lib/openapi-utils.ts | 8 +- lib/tools/base.ts | 6 +- lib/tools/index.ts | 2 +- lib/tools/linkedin.ts | 294 +++++++++++++++++++++++++ lib/tools/microsoft-teams.ts | 298 -------------------------- public/logos/linkedin-logo.png | Bin 0 -> 20371 bytes public/logos/microsoft-teams-logo.png | Bin 33486 -> 0 bytes 18 files changed, 490 insertions(+), 500 deletions(-) create mode 100644 lib/tools/linkedin.ts delete mode 100644 lib/tools/microsoft-teams.ts create mode 100644 public/logos/linkedin-logo.png delete mode 100644 public/logos/microsoft-teams-logo.png diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index c500c1c..5fb16b1 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -279,7 +279,7 @@ export async function POST(request: Request) { const crmContactsTool = toolRegistry.getTool("crm-contacts"); const googleMeetTool = toolRegistry.getTool("google-meet"); const slackTool = toolRegistry.getTool("slack"); - const teamsChatTool = toolRegistry.getTool("microsoft-teams"); + const linkedInTool = toolRegistry.getTool("linkedin"); // Define the tools object const toolsObject: any = { @@ -908,86 +908,105 @@ export async function POST(request: Request) { } }, }, - // Add Microsoft Teams meeting tool - createTeamsChat: { + // Add LinkedIn tool + useLinkedIn: { description: - "Create a Microsoft Teams chat group and send an initial message", + "Interact with LinkedIn - send messages, create posts, and manage connections", parameters: z.object({ - title: z.string().describe("Chat group title"), - description: z.string().describe("Initial message content"), - startTime: z + action: z.enum([ + "send_message", + "create_post", + "send_connection", + "search_people", + ]), + recipientEmail: z .string() + .optional() .describe( - "Time reference in ISO format (if scheduling discussion)" - ), - duration: z - .number() - .describe("Duration in minutes (if scheduling discussion)"), - attendees: z - .array(z.string()) - .describe( - "List of attendee email addresses to include in the chat" + "Email of the LinkedIn recipient when sending messages or connection requests" ), - timeZone: z + recipientId: z .string() .optional() - .describe("Time zone (optional, defaults to user's timezone)"), - settings: z - .object({ - chatType: z - .enum(["group", "oneOnOne", "meeting"]) - .optional() - .describe("Type of chat to create"), - allowNewTimeProposals: z.boolean().optional(), - }) - .optional(), + .describe("LinkedIn ID of the recipient when sending messages"), + message: z + .string() + .optional() + .describe("Message content for messages or connection requests"), + text: z.string().optional().describe("Content for LinkedIn posts"), + visibility: z + .enum(["public", "connections", "group"]) + .optional() + .describe("Visibility setting for posts"), + profileUrl: z + .string() + .optional() + .describe("LinkedIn profile URL for connection requests"), + query: z + .string() + .optional() + .describe("Search query for finding people on LinkedIn"), + limit: z.number().optional().describe("Limit for search results"), }), execute: async (data: any) => { try { - if (!teamsChatTool) { + if (!linkedInTool) { return { success: false, - error: "Microsoft Teams tool not available", - message: - "Unable to create Teams chats. Please connect your Microsoft account.", + error: "LinkedIn tool not available", ui: { type: "connection_required", - service: "microsoft-teams", + service: "linkedin", message: - "Please connect your Microsoft account to create Teams chats", + "Please connect your LinkedIn account to use this feature", connectButton: { - text: "Connect Microsoft Teams", - action: "connection://microsoft-teams", + text: "Connect LinkedIn", + action: "connection://linkedin", }, }, }; } - // Use the client's timezone if no timeZone was specified - if (!data.timeZone && timezone !== "UTC") { - data.timeZone = timezone; - } + // Transform the flat parameters into the expected LinkedIn action format + let linkedInAction: any = { action: data.action }; - const result = await teamsChatTool.execute(userId, { - title: data.title, - description: data.description, - startTime: data.startTime, - duration: data.duration, - attendees: data.attendees, - timeZone: data.timeZone, - settings: data.settings, - }); + // Add appropriate data based on action type + if (data.action === "send_message") { + linkedInAction.data = { + recipientEmail: data.recipientEmail, + recipientId: data.recipientId, + message: data.message, + }; + } else if (data.action === "create_post") { + linkedInAction.data = { + text: data.text, + visibility: data.visibility, + }; + } else if (data.action === "send_connection") { + linkedInAction.data = { + email: data.recipientEmail, + profileUrl: data.profileUrl, + message: data.message, + }; + } else if (data.action === "search_people") { + linkedInAction.query = data.query; + linkedInAction.limit = data.limit; + } - return result; - } catch (error) { + return await linkedInTool.execute(userId, linkedInAction); + } catch (err) { + console.error("Error using LinkedIn:", err); return { success: false, error: - error instanceof Error - ? error.message - : "Unknown error creating Teams chat", - message: - "There was an error creating your Teams chat. Please try again later.", + err instanceof Error + ? err.message + : "Unknown error using LinkedIn", + ui: { + type: "error", + message: + "There was an error with the LinkedIn operation. Please try again later.", + }, }; } }, diff --git a/app/api/oauth/connections/route.ts b/app/api/oauth/connections/route.ts index 4549f86..a2a129e 100644 --- a/app/api/oauth/connections/route.ts +++ b/app/api/oauth/connections/route.ts @@ -5,7 +5,6 @@ import { getGoogleMeetToken, getCRMToken, getSlackToken, - getMicrosoftTeamsToken, } from "@/lib/descope"; import { trackOAuthEvent, trackError } from "@/lib/analytics"; import { getChatById, getChatMessages } from "@/lib/db/queries"; @@ -43,45 +42,35 @@ export async function GET() { // Get the status of all OAuth connections using default operations from lib/descope.ts console.log("Starting OAuth provider token fetch"); - const [ - googleCalendar, - googleDocs, - googleMeet, - customCrm, - slack, - microsoftTeams, - ] = await Promise.all([ - getGoogleCalendarToken(userId).catch((e) => { - console.error("Error fetching Google Calendar token:", e); - console.error("Google Calendar error details:", { - name: e.name, - message: e.message, - status: e.status, - stack: e.stack, - }); - return { error: e.message, connected: false, originalError: e }; - }), - getGoogleDocsToken(userId).catch((e) => { - console.error("Error fetching Google Docs token:", e); - return { error: e.message, connected: false }; - }), - getGoogleMeetToken(userId).catch((e) => { - console.error("Error fetching Google Meet token:", e); - return { error: e.message, connected: false }; - }), - getCRMToken(userId).catch((e) => { - console.error("Error fetching CRM token:", e); - return { error: e.message, connected: false }; - }), - getSlackToken(userId).catch((e) => { - console.error("Error fetching Slack token:", e); - return { error: e.message, connected: false }; - }), - getMicrosoftTeamsToken(userId).catch((e) => { - console.error("Error fetching Microsoft Teams token:", e); - return { error: e.message, connected: false }; - }), - ]); + const [googleCalendar, googleDocs, googleMeet, customCrm, slack] = + await Promise.all([ + getGoogleCalendarToken(userId).catch((e) => { + console.error("Error fetching Google Calendar token:", e); + console.error("Google Calendar error details:", { + name: e.name, + message: e.message, + status: e.status, + stack: e.stack, + }); + return { error: e.message, connected: false, originalError: e }; + }), + getGoogleDocsToken(userId).catch((e) => { + console.error("Error fetching Google Docs token:", e); + return { error: e.message, connected: false }; + }), + getGoogleMeetToken(userId).catch((e) => { + console.error("Error fetching Google Meet token:", e); + return { error: e.message, connected: false }; + }), + getCRMToken(userId).catch((e) => { + console.error("Error fetching CRM token:", e); + return { error: e.message, connected: false }; + }), + getSlackToken(userId).catch((e) => { + console.error("Error fetching Slack token:", e); + return { error: e.message, connected: false }; + }), + ]); // Process token responses const processConnection = (response: any) => { @@ -165,7 +154,6 @@ export async function GET() { "google-meet": processConnection(googleMeet), "custom-crm": processConnection(customCrm), slack: processConnection(slack), - "microsoft-teams": processConnection(microsoftTeams), }; trackOAuthEvent("connection_successful", { @@ -176,7 +164,6 @@ export async function GET() { googleMeetConnected: connections["google-meet"].connected, crmConnected: connections["custom-crm"].connected, slackConnected: connections["slack"].connected, - microsoftTeamsConnected: connections["microsoft-teams"].connected, }); return Response.json({ connections }); diff --git a/app/connections/page.tsx b/app/connections/page.tsx index 79cc831..8282375 100644 --- a/app/connections/page.tsx +++ b/app/connections/page.tsx @@ -85,9 +85,9 @@ export default function ConnectionsPage() { connected: false, }, { - id: "microsoft-teams", - name: "Microsoft Teams", - icon: "/logos/microsoft-teams-logo.png", + id: "linkedin", + name: "LinkedIn", + icon: "/logos/linkedin-logo.png", connected: false, }, ]); diff --git a/app/page.tsx b/app/page.tsx index 4869b8e..2756a9f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -55,8 +55,8 @@ type PromptType = | "slack" | "summarize-deal" | "create-google-meet" - | "microsoft-teams" - | "add-custom-tool"; + | "add-custom-tool" + | "linkedin"; interface PromptExplanation { title: string; @@ -200,15 +200,55 @@ const promptExplanations: Record = { ], apis: ["Slack API"], }, + linkedin: { + title: "LinkedIn Integration", + description: "Access LinkedIn content and interaction data", + logo: "/logos/linkedin-logo.png", + examples: [ + "Find my recent LinkedIn posts", + "Show me the engagement stats on my last LinkedIn post", + "Check who interacted with my LinkedIn document", + "Get a summary of my LinkedIn content performance", + "View my LinkedIn media uploads from the past month", + ], + steps: [ + { + title: "User Requests LinkedIn Information", + description: + "The user asks to retrieve information about their LinkedIn content and interactions.", + }, + { + title: "Authentication Check", + description: + "The assistant verifies the user is authenticated and has connected LinkedIn via OAuth with w_member_social scope.", + }, + { + title: "LinkedIn API Access", + description: + "Using the stored OAuth token from Descope, the assistant makes a secure API call to LinkedIn's content endpoints.", + }, + { + title: "Data Retrieval", + description: + "The assistant retrieves the requested information about posts, media, or interaction statistics.", + }, + { + title: "Information Presentation", + description: + "The assistant presents the retrieved LinkedIn data in a clear, organized format.", + }, + ], + apis: ["LinkedIn Posts API", "LinkedIn Media API"], + }, "summarize-deal": { title: "Summarize Deal to Google Docs", description: "Create comprehensive deal summaries and save them directly to Google Docs for sharing and collaboration", logo: "/logos/google-docs.png", examples: [ - "Summarize the Enterprise Software License deal with John Doe", - "Create a deal report for the Cloud Migration Project with Jane Lane", - "Generate a summary of the IT Infrastructure Upgrade deal with Michael Chen", + // "Summarize the Enterprise Software License deal with John Doe", + // "Create a deal report for the Cloud Migration Project with Jane Lane", + // "Generate a summary of the IT Infrastructure Upgrade deal with Michael Chen", ], steps: [ { @@ -278,46 +318,6 @@ const promptExplanations: Record = { ], apis: ["Google Calendar API"], }, - "microsoft-teams": { - title: "Microsoft Teams Chat", - description: - "Create Teams chat groups and send messages to collaborate with your team", - logo: "/logos/microsoft-teams-logo.png", - examples: [ - "Create a Teams chat with Sarah and Tom about the sales proposal", - "Start a Microsoft Teams discussion with the marketing team", - "Set up a Teams chat group for the product launch team", - "Create a Teams chat to discuss the client meeting tomorrow", - ], - steps: [ - { - title: "User Requests Teams Chat Creation", - description: - "The user asks to create a Microsoft Teams chat group with specific participants.", - }, - { - title: "Authentication Check", - description: - "The assistant verifies the user is authenticated and has connected Microsoft Teams via OAuth.", - }, - { - title: "Teams API Access", - description: - "Using the stored OAuth token from Descope, the assistant makes a secure API call to Microsoft Graph API.", - }, - { - title: "Chat Group Creation", - description: - "A new Teams chat group is created with the specified participants.", - }, - { - title: "Initial Message Sending", - description: - "The assistant sends an initial message to the chat group with the specified details or meeting information.", - }, - ], - apis: ["Microsoft Graph API"], - }, "add-custom-tool": { title: "Add Your Own Tool", description: @@ -1095,16 +1095,13 @@ export default function Home() { ), }, { - id: "microsoft-teams", - title: "Microsoft Teams Chat", - description: "Create Teams chat groups with your colleagues", - logo: "/logos/microsoft-teams-logo.png", + id: "linkedin", + title: "LinkedIn Integration", + description: "Access LinkedIn content and interaction data", + logo: "/logos/linkedin-logo.png", action: () => checkOAuthAndPrompt(() => - usePredefinedPrompt( - "Create a Teams chat with Sarah and Tom about the Q4 strategy", - "microsoft-teams" - ) + usePredefinedPrompt("Find my recent LinkedIn posts", "linkedin") ), }, { diff --git a/components/chat.tsx b/components/chat.tsx index f0278f1..4782a13 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -487,6 +487,7 @@ export default function Chat({ if (content.includes("meet")) service = "google-meet"; if (content.includes("slack")) service = "slack"; if (content.includes("zoom")) service = "zoom"; + if (content.includes("linkedin")) service = "linkedin"; isReconnect = content.includes("additional permission") || @@ -539,6 +540,8 @@ export default function Chat({ "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/meetings.space.created" ); + } else if (service === "linkedin") { + requiredScopes.push("r_emailaddress", "r_liteprofile", "w_member_social"); } let messageText = isReconnect @@ -579,6 +582,8 @@ export default function Chat({ return "Slack"; case "zoom": return "Zoom"; + case "linkedin": + return "LinkedIn"; default: return provider.replace(/-/g, " "); } @@ -644,6 +649,7 @@ export default function Chat({ else if (serviceName.includes("crm")) service = "custom-crm"; else if (serviceName.includes("slack")) service = "slack"; else if (serviceName.includes("zoom")) service = "zoom"; + else if (serviceName.includes("linkedin")) service = "linkedin"; message = { ...message, @@ -706,6 +712,7 @@ export default function Chat({ message.content.toLowerCase().includes("documents") || message.content.toLowerCase().includes("drive") || message.content.toLowerCase().includes("slack") || + message.content.toLowerCase().includes("linkedin") || message.content.toLowerCase().includes("meet") || message.content.toLowerCase().includes("google meet"))) || message.content.includes("](connection:") || diff --git a/components/prompt-trigger.tsx b/components/prompt-trigger.tsx index c263330..cdc45db 100644 --- a/components/prompt-trigger.tsx +++ b/components/prompt-trigger.tsx @@ -8,14 +8,13 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { Calendar, FileText, MessageSquare, Search, Video } from "lucide-react"; +import { Calendar, FileText, Search, Video } from "lucide-react"; const icons = { "crm-lookup": Search, "schedule-meeting": Calendar, "create-google-meet": Video, "summarize-deal": FileText, - "microsoft-teams": MessageSquare, }; interface PromptTriggerProps { diff --git a/components/user-menu.tsx b/components/user-menu.tsx index 65ecb3e..74a37d2 100644 --- a/components/user-menu.tsx +++ b/components/user-menu.tsx @@ -71,9 +71,9 @@ export default function UserMenu({ onProfileClick }: UserMenuProps) { connected: false, }, { - id: "microsoft-teams", - name: "Microsoft Teams", - icon: "/logos/microsoft-teams-logo.png", + id: "linkedin", + name: "LinkedIn", + icon: "/logos/linkedin-logo.png", connected: false, }, ]); diff --git a/hooks/use-connection-notification.tsx b/hooks/use-connection-notification.tsx index 3deb96f..79347a1 100644 --- a/hooks/use-connection-notification.tsx +++ b/hooks/use-connection-notification.tsx @@ -70,24 +70,23 @@ const providers = { "team chat", ], }, - "microsoft-teams": { - id: "microsoft-teams", - name: "Microsoft Teams", - icon: "/logos/microsoft-teams-logo.png", - scopes: [ - "ChannelMember.ReadWrite.All", - "ChannelMessage.Send", - "Chat.ReadWrite.All", - "ChatMessage.Send", - "offline_access", - ], + linkedin: { + id: "linkedin", + name: "LinkedIn", + icon: "/logos/linkedin-logo.png", + scopes: ["w_member_social"], keywords: [ - "teams", - "microsoft teams", - "teams meeting", - "video conference", - "teams call", - "online meeting", + "linkedin", + "post", + "share", + "upload", + "image", + "document", + "content", + "professional", + "article", + "media", + "announcement", ], }, }; diff --git a/lib/connection-manager.ts b/lib/connection-manager.ts index 0d9477c..473e9a5 100644 --- a/lib/connection-manager.ts +++ b/lib/connection-manager.ts @@ -7,8 +7,7 @@ export type OAuthProvider = | "google-docs" | "google-meet" | "custom-crm" - | "slack" - | "microsoft-teams"; + | "slack"; const CONNECTION_KEY_PREFIX = "oauth_connection_"; @@ -49,7 +48,6 @@ export function getAllConnectionStatuses(): Record { "google-meet": false, "custom-crm": false, slack: false, - "microsoft-teams": false, }; } @@ -59,7 +57,6 @@ export function getAllConnectionStatuses(): Record { "google-meet", "custom-crm", "slack", - "microsoft-teams", ]; return providers.reduce((statuses, provider) => { @@ -80,7 +77,6 @@ export function disconnectAll(): void { "google-meet", "custom-crm", "slack", - "microsoft-teams", ]; providers.forEach((provider) => { diff --git a/lib/descope.ts b/lib/descope.ts index 0692ee4..3fbd33c 100644 --- a/lib/descope.ts +++ b/lib/descope.ts @@ -300,13 +300,3 @@ export async function getSlackToken( withRefreshToken: false, }); } - -// Add Microsoft Teams token function -export async function getMicrosoftTeamsToken( - userId: string, - operation: string = "check_connection" -) { - return getOAuthToken(userId, "microsoft-teams", operation, { - withRefreshToken: false, - }); -} diff --git a/lib/oauth-utils.ts b/lib/oauth-utils.ts index 62165e8..2c8c7c0 100644 --- a/lib/oauth-utils.ts +++ b/lib/oauth-utils.ts @@ -67,13 +67,7 @@ export const DEFAULT_SCOPES: Record = { ], "custom-crm": ["openid", "contacts:read", "deals:read"], slack: ["chat:write", "channels:manage", "users:read"], - "microsoft-teams": [ - "ChannelMember.ReadWrite.All", - "ChannelMessage.Send", - "Chat.ReadWrite.All", - "ChatMessage.Send", - "offline_access", - ], + linkedin: ["r_emailaddress", "r_basicprofile", "w_member_social"], }; export async function getOAuthTokenWithScopeValidation( diff --git a/lib/openapi-utils.ts b/lib/openapi-utils.ts index 9e1a4ca..c073156 100644 --- a/lib/openapi-utils.ts +++ b/lib/openapi-utils.ts @@ -83,7 +83,7 @@ export async function getRequiredScopes( "custom-crm": { "contacts.list": ["contacts.read"], "deals.list": ["deals.read"], - connect: ["openid", "contacts:read", "deals:read"], // Both for connection + connect: ["openid", "contacts:read", "deals:read"], }, slack: { "chat:write": ["chat:write"], @@ -91,6 +91,12 @@ export async function getRequiredScopes( "users:read": ["users:read"], connect: ["chat:write", "channels:manage", "users:read"], }, + linkedin: { + r_emailaddress: ["r_emailaddress"], + r_basicprofile: ["r_basicprofile"], + w_member_social: ["w_member_social"], + connect: ["r_emailaddress", "r_basicprofile", "w_member_social"], + }, }; // Check if we have scopes for this specific operation diff --git a/lib/tools/base.ts b/lib/tools/base.ts index fa41c10..a0e41fc 100644 --- a/lib/tools/base.ts +++ b/lib/tools/base.ts @@ -366,7 +366,7 @@ export type OAuthProvider = | "custom-crm" | "slack" | "zoom" - | "microsoft-teams"; + | "linkedin"; // Create standardized connection request for OAuth providers export function createConnectionRequest(options: { @@ -447,8 +447,8 @@ export function createConnectionRequest(options: { return "Slack"; case "zoom": return "Zoom"; - case "microsoft-teams": - return "Microsoft Teams"; + case "linkedin": + return "LinkedIn"; default: return String(provider).replace(/-/g, " "); } diff --git a/lib/tools/index.ts b/lib/tools/index.ts index 7cbc26c..671fb6d 100644 --- a/lib/tools/index.ts +++ b/lib/tools/index.ts @@ -6,7 +6,7 @@ import "./calendar"; import "./calendar-list"; import "./google-meet"; import "./slack"; -import "./microsoft-teams"; +import "./linkedin"; // Export the tool registry for direct access export { toolRegistry } from "./base"; diff --git a/lib/tools/linkedin.ts b/lib/tools/linkedin.ts new file mode 100644 index 0000000..9c95e03 --- /dev/null +++ b/lib/tools/linkedin.ts @@ -0,0 +1,294 @@ +import { + Tool, + ToolConfig, + ToolResponse, + toolRegistry, + createConnectionRequest, +} from "./base"; +import { getOAuthTokenWithScopeValidation } from "../oauth-utils"; +import { getRequiredScopes } from "@/lib/openapi-utils"; + +export interface LinkedInPost { + text: string; + visibility?: "public" | "connections" | "group"; + imageUrl?: string; + documentUrl?: string; +} + +export interface LinkedInMediaUpload { + mediaType: "image" | "document"; + title: string; + description?: string; + filePath?: string; + fileUrl?: string; +} + +type LinkedInAction = + | { action: "create_post"; data: LinkedInPost } + | { action: "upload_media"; data: LinkedInMediaUpload } + | { action: "update_post"; postId: string; data: Partial }; + +export class LinkedInTool extends Tool { + config: ToolConfig = { + id: "linkedin", + name: "LinkedIn", + description: + "Create and manage posts, upload media, and interact with content on LinkedIn", + scopes: ["w_member_social"], + requiredFields: ["action"], + optionalFields: ["data", "postId"], + capabilities: [ + "Create LinkedIn posts", + "Upload images to LinkedIn", + "Share documents on LinkedIn", + "Manage post content and visibility", + ], + oauthConfig: { + provider: "linkedin", + defaultScopes: ["w_member_social"], + requiredScopes: ["w_member_social"], + scopeMapping: { + create_post: ["w_member_social"], + upload_media: ["w_member_social"], + update_post: ["w_member_social"], + }, + }, + }; + + validate(data: LinkedInAction): ToolResponse | null { + if (data.action === "create_post") { + if (!data.data.text) { + return { + success: false, + error: "Missing post content", + needsInput: { + field: "text", + message: "Please provide the content for your LinkedIn post", + }, + }; + } + } else if (data.action === "upload_media") { + if (!data.data.mediaType) { + return { + success: false, + error: "Missing media type", + needsInput: { + field: "mediaType", + message: + "Please specify whether you're uploading an image or document", + }, + }; + } + if (!data.data.title) { + return { + success: false, + error: "Missing media title", + needsInput: { + field: "title", + message: "Please provide a title for your media upload", + }, + }; + } + if (!data.data.fileUrl && !data.data.filePath) { + return { + success: false, + error: "Missing file source", + needsInput: { + field: "fileSource", + message: "Please provide either a file URL or file path", + }, + }; + } + } else if (data.action === "update_post") { + if (!data.postId) { + return { + success: false, + error: "Missing post ID", + needsInput: { + field: "postId", + message: "Please provide the ID of the post to update", + }, + }; + } + if (Object.keys(data.data).length === 0) { + return { + success: false, + error: "Missing update data", + needsInput: { + field: "data", + message: "Please provide the content to update in the post", + }, + }; + } + } + + return null; + } + + // Add this method to get required scopes for specific operations + getToolScopesForOperation(action: string): string[] { + const scopeMapping = { + create_post: ["w_member_social"], + upload_media: ["w_member_social"], + update_post: ["w_member_social"], + }; + + return ( + scopeMapping[action as keyof typeof scopeMapping] || + this.config.scopes || + [] + ); + } + + async execute( + userId: string, + data: LinkedInAction, + sessionId?: string + ): Promise { + try { + console.log(`[LinkedInTool] Executing ${data.action}:`, data); + + // Get the required scopes for the action + const requiredScopes = this.getToolScopesForOperation(data.action); + + // Get the OAuth token with scope validation + const tokenResponse = await getOAuthTokenWithScopeValidation( + userId, + "linkedin", + { + appId: "linkedin", + userId, + scopes: requiredScopes, + operation: "tool_calling", + } + ); + + if (!tokenResponse || "error" in tokenResponse) { + // Extract scope information if available + const currentScopes = + "currentScopes" in tokenResponse + ? tokenResponse.currentScopes + : undefined; + + return createConnectionRequest({ + provider: "linkedin", + isReconnect: currentScopes && currentScopes.length > 0, + requiredScopes, + currentScopes, + customMessage: `Please connect your LinkedIn account to perform the ${data.action} action.`, + }); + } + + // Extract the access token + const accessToken = tokenResponse.token?.accessToken; + + switch (data.action) { + case "create_post": + return await this.createPost(accessToken!, data.data); + + case "upload_media": + return await this.uploadMedia(accessToken!, data.data); + + case "update_post": + return await this.updatePost(accessToken!, data.postId, data.data); + + default: + return { + success: false, + error: "Unknown action type", + status: "error", + }; + } + } catch (error: any) { + if ( + error.message?.includes("OAuth token not found") || + error.message?.includes("missing required scopes") + ) { + const requiredScopes = this.getToolScopesForOperation(data.action); + return createConnectionRequest({ + provider: "linkedin", + isReconnect: false, + requiredScopes, + customMessage: `Please connect your LinkedIn account to perform the ${data.action} action.`, + }); + } + console.error("LinkedIn Tool Error:", error); + return { + success: false, + error: `Failed to execute LinkedIn action: ${ + error.message || "Unknown error" + }`, + status: "error", + }; + } + } + + // LinkedIn API methods + private async createPost( + accessToken: string, + postData: LinkedInPost + ): Promise { + // In a real implementation, this would call the LinkedIn API + console.log("[LinkedInTool] Creating post:", postData); + + // Simulate successful post creation + return { + success: true, + data: { + postId: `post-${Date.now()}`, + published: true, + visibility: postData.visibility || "connections", + timestamp: new Date().toISOString(), + url: `https://www.linkedin.com/feed/update/activity-${Date.now()}`, + text: "LinkedIn post created successfully", + }, + }; + } + + private async uploadMedia( + accessToken: string, + mediaData: LinkedInMediaUpload + ): Promise { + // In a real implementation, this would call the LinkedIn API + console.log("[LinkedInTool] Uploading media:", mediaData); + + // Simulate successful media upload + return { + success: true, + data: { + mediaId: `media-${Date.now()}`, + mediaType: mediaData.mediaType, + title: mediaData.title, + description: mediaData.description || "", + url: `https://www.linkedin.com/media/${ + mediaData.mediaType + }-${Date.now()}`, + text: `LinkedIn ${mediaData.mediaType} uploaded successfully`, + }, + }; + } + + private async updatePost( + accessToken: string, + postId: string, + updateData: Partial + ): Promise { + // In a real implementation, this would call the LinkedIn API + console.log("[LinkedInTool] Updating post:", { postId, updateData }); + + // Simulate successful post update + return { + success: true, + data: { + postId: postId, + updated: true, + timestamp: new Date().toISOString(), + url: `https://www.linkedin.com/feed/update/${postId}`, + text: "LinkedIn post updated successfully", + }, + }; + } +} + +// Register the LinkedIn tool +toolRegistry.register(new LinkedInTool()); diff --git a/lib/tools/microsoft-teams.ts b/lib/tools/microsoft-teams.ts deleted file mode 100644 index 6ee2563..0000000 --- a/lib/tools/microsoft-teams.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { - Tool, - ToolConfig, - ToolResponse, - toolRegistry, - createConnectionRequest, -} from "./base"; -import { getOAuthTokenWithScopeValidation } from "../oauth-utils"; -import { getCurrentDateContext } from "@/lib/date-utils"; - -export interface TeamsEvent { - title: string; - description?: string; - startTime: string; - duration: number; - attendees?: string[]; - timeZone?: string; - settings?: { - chatType?: "group" | "oneOnOne" | "meeting"; - allowNewTimeProposals?: boolean; - }; -} - -export class TeamsChatTool extends Tool { - config: ToolConfig = { - id: "microsoft-teams", - name: "Microsoft Teams", - description: "Create Microsoft Teams chats and send messages", - scopes: [ - "ChannelMember.ReadWrite.All", - "ChannelMessage.Send", - "Chat.ReadWrite.All", - "ChatMessage.Send", - "offline_access", - ], - requiredFields: ["title", "startTime", "duration"], - optionalFields: ["description", "attendees", "timeZone", "settings"], - capabilities: [ - "Create Microsoft Teams meetings", - "Generate Teams meeting links", - "Schedule video conferences", - "Manage meeting settings", - ], - oauthConfig: { - provider: "microsoft-teams", - defaultScopes: [ - "ChannelMember.ReadWrite.All", - "ChannelMessage.Send", - "Chat.ReadWrite.All", - "ChatMessage.Send", - "offline_access", - ], - }, - }; - - validate(data: TeamsEvent): ToolResponse | null { - // Get current date context for validation - const dateContext = getCurrentDateContext(); - - if (!data.title) { - return { - success: false, - error: "Missing title", - needsInput: { - field: "title", - message: "Please provide a meeting title", - }, - }; - } - - if (!data.startTime) { - return { - success: false, - error: "Missing start time", - needsInput: { - field: "startTime", - message: `Please provide a start time. Today is ${dateContext.currentDate}.`, - }, - }; - } - - if (!data.duration) { - return { - success: false, - error: "Missing duration", - needsInput: { - field: "duration", - message: "Please provide a meeting duration in minutes", - }, - }; - } - - return null; - } - - async execute(userId: string, data: TeamsEvent): Promise { - try { - // Include date context when processing - const dateContext = getCurrentDateContext(); - console.log("Creating Microsoft Teams meeting:", { - title: data.title, - startTime: data.startTime, - duration: data.duration, - currentDate: dateContext.currentDate, - }); - - // Get OAuth token for Microsoft Graph API - const tokenResponse = await getOAuthTokenWithScopeValidation( - userId, - "microsoft-teams", - { - appId: "microsoft-teams", - userId, - scopes: this.config.scopes, - operation: "tool_calling", - } - ); - - if (!tokenResponse || "error" in tokenResponse) { - // Extract scope information if available - const requiredScopes = - "requiredScopes" in tokenResponse - ? tokenResponse.requiredScopes - : this.config.scopes; - const currentScopes = - "currentScopes" in tokenResponse - ? tokenResponse.currentScopes - : undefined; - - // Use standardized connection request - return createConnectionRequest({ - provider: "microsoft-teams", - isReconnect: currentScopes && currentScopes.length > 0, - requiredScopes: requiredScopes, - currentScopes: currentScopes, - customMessage: - "Please connect your Microsoft account to create Teams meetings", - }); - } - - // Extract the access token - const accessToken = tokenResponse.token?.accessToken; - - // Calculate end time from duration - const startTime = new Date(data.startTime); - const endTime = new Date(startTime.getTime() + data.duration * 60000); - - // First, create a chat with all the attendees - const attendeesList = data.attendees || []; - - // Create chat members array for the API request - const chatMembers = attendeesList.map((email) => ({ - "@odata.type": "#microsoft.graph.aadUserConversationMember", - roles: ["owner"], - "user@odata.bind": `https://graph.microsoft.com/v1.0/users('${email}')`, - })); - - // Create the chat - const chatPayload = { - chatType: "group", - topic: data.title, - members: chatMembers, - }; - - // Create a chat using Microsoft Graph API - const chatResponse = await fetch( - "https://graph.microsoft.com/v1.0/chats", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, - }, - body: JSON.stringify(chatPayload), - } - ); - - if (!chatResponse.ok) { - const errorData = await chatResponse.json(); - console.error("Microsoft Graph API error creating chat:", errorData); - - // Check if this is a scope or permission error - if (chatResponse.status === 401 || chatResponse.status === 403) { - return createConnectionRequest({ - provider: "microsoft-teams", - isReconnect: true, - requiredScopes: this.config.scopes, - customMessage: - "You need additional permissions to create Teams chats. Please reconnect with the required scopes.", - }); - } - - throw new Error( - `Failed to create Teams chat: ${ - errorData.error?.message || chatResponse.statusText - }` - ); - } - - const chatData = await chatResponse.json(); - const chatId = chatData.id; - - // Now send a message to the chat with meeting details - const messageContent = ` -${data.title} -

${data.description || ""}

-

Start: ${startTime.toLocaleString()}

-

End: ${endTime.toLocaleString()}

-

Duration: ${data.duration} minutes

-`; - - // Send a message to the chat - const messagePayload = { - body: { - contentType: "html", - content: messageContent, - }, - }; - - const messageResponse = await fetch( - `https://graph.microsoft.com/v1.0/chats/${chatId}/messages`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${accessToken}`, - }, - body: JSON.stringify(messagePayload), - } - ); - - if (!messageResponse.ok) { - const errorData = await messageResponse.json(); - console.error("Microsoft Graph API error sending message:", errorData); - - // We'll still continue since at least the chat was created - console.warn("Failed to send initial message to Teams chat"); - } - - // Get chat webUrl - const chatUrl = chatData.webUrl; - - return { - success: true, - data: { - chatId: chatId, - joinUrl: chatUrl, - formattedMessage: `Microsoft Teams chat "${data.title}" created successfully! Join here: [Join Teams Chat](${chatUrl})`, - meetingInfo: { - topic: data.title, - startTime: startTime.toISOString(), - duration: data.duration, - timezone: data.timeZone || "UTC", - joinUrl: chatUrl, - }, - }, - }; - } catch (error) { - console.error("Error creating Microsoft Teams meeting:", error); - - // Check if this is an insufficient permissions error - const isInsufficientPermissions = - error instanceof Error && - (error.message.includes("Insufficient Permission") || - error.message.includes("403") || - (error as any).code === 403); - - if (isInsufficientPermissions) { - // Use standardized connection request for reconnection - return createConnectionRequest({ - provider: "microsoft-teams", - isReconnect: true, - requiredScopes: this.config.scopes, - customMessage: - "You need additional permissions to create Teams chats. Please reconnect with the required scopes.", - }); - } - - // Default error response - return { - success: false, - status: "error", - error: - error instanceof Error - ? error.message - : "Failed to create Microsoft Teams chat", - ui: { - type: "error", - message: - "There was an error creating your Teams chat. Please try again later.", - }, - }; - } - } -} - -// Register the tool with the registry -toolRegistry.register(new TeamsChatTool()); diff --git a/public/logos/linkedin-logo.png b/public/logos/linkedin-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..963516707c2180c157950dd209f29ff666b4ee27 GIT binary patch literal 20371 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 literal 0 HcmV?d00001 diff --git a/public/logos/microsoft-teams-logo.png b/public/logos/microsoft-teams-logo.png deleted file mode 100644 index 3cd76acba52cbf23859fc42c623c420004be6423..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33486 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