diff --git a/tools/email-imap-fetcher/metadata.json b/tools/email-imap-fetcher/metadata.json index 741a4f4b..685a617f 100644 --- a/tools/email-imap-fetcher/metadata.json +++ b/tools/email-imap-fetcher/metadata.json @@ -29,6 +29,11 @@ "type": "integer", "description": "The port number for the IMAP server (defaults to 993 for IMAPS)", "default": 993 + }, + "ssl": { + "type": "boolean", + "description": "Whether to use SSL for the IMAP connection (defaults to true)", + "default": true } }, "required": [ diff --git a/tools/email-imap-fetcher/test.py b/tools/email-imap-fetcher/test.py new file mode 100644 index 00000000..922e4ea8 --- /dev/null +++ b/tools/email-imap-fetcher/test.py @@ -0,0 +1,31 @@ +import asyncio +from tool import run, CONFIG, INPUTS +# Assuming the classes and functions are imported from your module +# from your_module import CONFIG, INPUTS, run + +async def test_imap_connection(): + # Create a CONFIG instance with fake credentials + config = CONFIG() + config.imap_server = "server.fakemail.com" # Replace with a valid IMAP server for testing + config.username = "shinkai.dev@fakemail.com" # Fake username + config.password = "fakepassword" # Fake password + config.port = 993 # Common port for IMAPS + config.ssl = True # Use SSL + + # Create an INPUTS instance + inputs = INPUTS() + inputs.from_date = None # Optional: set to a date string if needed + inputs.to_date = None # Optional: set to a date string if needed + + # Run the function and capture the output + output = await run(config, inputs) + print("Hello World") + # Print the output + print("Login Status:", output.login_status) + print("Emails Retrieved:", len(output.emails)) + for email in output.emails: + print(email['subject'] + " " + email['sender'] + " " + str(email['date'])) + +# Run the test +if __name__ == "__main__": + asyncio.run(test_imap_connection()) diff --git a/tools/email-imap-fetcher/tool.py b/tools/email-imap-fetcher/tool.py index 51b05d44..cc7322e9 100644 --- a/tools/email-imap-fetcher/tool.py +++ b/tools/email-imap-fetcher/tool.py @@ -8,6 +8,7 @@ class CONFIG: username: str password: str port: int = 143 # Default port for IMAPS + ssl: bool = False # New flag to specify SSL usage class INPUTS: from_date: Optional[str] @@ -25,9 +26,14 @@ class Email: async def run(config: CONFIG, inputs: INPUTS) -> OUTPUT: output = OUTPUT() + output.login_status = "N/A" output.emails = [] try: - imap = imaplib.IMAP4(config.imap_server, config.port) # Use config port + # Use SSL if the ssl flag is set to True + if config.ssl: + imap = imaplib.IMAP4_SSL(config.imap_server, config.port) + else: + imap = imaplib.IMAP4(config.imap_server, config.port) # Use config port except Exception as ee: output.login_status = 'IMAP4 INIT FAILED - ' + str(ee) return output diff --git a/tools/email-responder/tool.ts b/tools/email-responder/tool.ts index 56b481ce..199d827c 100644 --- a/tools/email-responder/tool.ts +++ b/tools/email-responder/tool.ts @@ -50,7 +50,7 @@ type OUTPUT = { // Helper function to escape user input function escapeSqlString(str: string): string { - return `'${str.replace(/'/g, "''")}'`; // Replaces single quotes with two single quotes + return `'${str.replace(/'/g, "''").replace('--', '').replace(';', '')}'`; // Replaces single quotes with two single quotes } export async function run(config: CONFIG, inputs: INPUTS): Promise { diff --git a/tools/email-sender/metadata.json b/tools/email-sender/metadata.json index 4aef45eb..a998be80 100644 --- a/tools/email-sender/metadata.json +++ b/tools/email-sender/metadata.json @@ -29,6 +29,11 @@ "sender_password": { "type": "string", "description": "The sender's email password" + }, + "ssl": { + "type": "boolean", + "description": "Whether to use SSL for the SMTP connection (defaults to false)", + "default": false } }, "required": [ diff --git a/tools/email-sender/test.py b/tools/email-sender/test.py new file mode 100644 index 00000000..4dedad20 --- /dev/null +++ b/tools/email-sender/test.py @@ -0,0 +1,28 @@ +import asyncio +from tool import CONFIG, INPUTS, run # Adjust the import based on your module structure + +async def test_email_sender(): + # Create a CONFIG instance with fake credentials + config = CONFIG() + config.smtp_server = "smtp.fakeemail.com" # Replace with a valid SMTP server for testing + config.port = 465 # Common port for SMTP with STARTTLS + config.sender_email = "shinkai.dev@fakeemail.com" # Fake sender email + config.sender_password = "fakepassword" # Fake password + config.ssl = True # Set to True if you want to test SSL + + # Create an INPUTS instance + inputs = INPUTS() + inputs.recipient_email = "eduardo@fakeemail.com" # Fake recipient email + inputs.subject = "Test Email" + inputs.body = "This is a test email sent using fake credentials." + + # Run the function and capture the output + output = await run(config, inputs) + + # Print the output + print("Email Sending Status:", output.status) + print("Message:", output.message) + +# Run the test +if __name__ == "__main__": + asyncio.run(test_email_sender()) diff --git a/tools/email-sender/tool.py b/tools/email-sender/tool.py index 0fde1f64..4931ee77 100644 --- a/tools/email-sender/tool.py +++ b/tools/email-sender/tool.py @@ -3,11 +3,17 @@ from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from datetime import datetime +import logging + +# Configure logging +logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s') + class CONFIG: smtp_server: str port: int = 465 # Default port for SMTP sender_email: str sender_password: str + ssl: bool = False # New flag to specify SSL usage for SMTP class INPUTS: recipient_email: str @@ -33,11 +39,23 @@ async def run(config: CONFIG, inputs: INPUTS) -> OUTPUT: msg.attach(MIMEText(inputs.body, 'plain')) try: - with smtplib.SMTP(config.smtp_server, config.port) as server: - server.login(config.sender_email, config.sender_password) - server.send_message(msg) - output.status = "success" - output.message = "Email sent successfully" + # Use SSL if the ssl flag is set to True + if config.ssl: + with smtplib.SMTP_SSL(config.smtp_server, config.port) as server: + server.login(config.sender_email, config.sender_password) + server.send_message(msg) + else: + with smtplib.SMTP(config.smtp_server, config.port) as server: + try: + server.starttls() # Upgrade to a secure connection + except Exception as e: + logging.error(f"Failed to upgrade to a secure connection: {e}") + # Attempt to login and send the message regardless of starttls success + server.login(config.sender_email, config.sender_password) + server.send_message(msg) + + output.status = "success" + output.message = "Email sent successfully" except Exception as e: output.message = f"An error occurred: {e}" diff --git a/tools/youtube-summary/index.test.ts b/tools/youtube-summary/index.test.ts index da173052..f6340a47 100644 --- a/tools/youtube-summary/index.test.ts +++ b/tools/youtube-summary/index.test.ts @@ -1,9 +1,5 @@ import { expect } from 'jsr:@std/expect/expect'; -import { definition, run } from './index.ts'; - -Deno.test('exists definition', () => { - expect(definition).toBeInstanceOf(Object); -}); +import { run } from './tool.ts'; Deno.test({ name: 'transcript video', @@ -11,13 +7,7 @@ Deno.test({ fn: async () => { const result = await run( { - apiUrl: - Deno.env.get('CI') === 'true' - ? 'https://api.openai.com/v1' - : 'http://127.0.0.1:11434', - apiKey: - Deno.env.get('CI') === 'true' ? Deno.env.get('OPEN_API_API_KEY') : '', - model: Deno.env.get('CI') === 'true' ? 'gpt-4o-mini' : '', + // No configuration needed }, { url: 'https://www.youtube.com/watch?v=SUj34OWkjXU', diff --git a/tools/youtube-summary/metadata.json b/tools/youtube-summary/metadata.json index 508a79e7..a9698010 100644 --- a/tools/youtube-summary/metadata.json +++ b/tools/youtube-summary/metadata.json @@ -1,70 +1,53 @@ { - "id": "youtube-summary", - "name": "YouTube Video Summary", - "description": "Summarizes a YouTube video. Provides a summary with organized sections and clickable timestamp links. Useful for quickly grasping main points, preparing for discussions, or efficient research. Example uses: summarizing tech talks, product reviews, or educational lectures. Parameters: url (string) - The full YouTube video URL to process.", - "author": "Shinkai", - "keywords": [ - "youtube", - "transcript", - "video", - "summary", - "sections", - "timestamp", - "links" - ], - "configurations": { - "type": "object", - "properties": { - "apiUrl": { - "type": "string", - "description": "The URL of the OpenAI compatible API endpoint for summary generation. Optional. Default: \"http://127.0.0.1:11435\".", - "nullable": true, - "example": "https://api.openai.com/v1" - }, - "apiKey": { - "type": "string", - "description": "The API key for the OpenAI compatible endpoint. Required if using a service that needs authentication.", - "nullable": true, - "example": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - }, - "model": { - "type": "string", - "description": "The name of the language model for summary generation. Optional. Default: \"llama3.1:8b-instruct-q4_1\".", - "nullable": true, - "example": "gpt-3.5-turbo" - } + "id": "youtube-summary", + "name": "Youtube Transcript Summarizer", + "description": "Fetches the transcript of a YouTube video and generates a formatted summary using an LLM.", + "author": "@@eduardosotomontaner.arb-sep-shinkai", + "version": "1.0.0", + "keywords": [ + "youtube", + "transcript", + "summary", + "video", + "LLM" + ], + "configurations": { + "type": "object", + "properties": {}, + "required": [] + }, + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The URL of the YouTube video" }, - "required": [] + "lang": { + "type": "string", + "description": "The language for the transcript (optional)" + } }, - "parameters": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "The full URL of the YouTube video to transcribe and summarize. Must be a valid and accessible YouTube video link.", - "example": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - }, - "lang": { - "type": "string", - "description": "The language code for the transcript in ISO 639-1 format (e.g. \"en\" for English). Optional. If not specified, will use the default available transcript.", - "example": "en", - "nullable": true - } - }, - "required": [ - "url" - ] + "required": [ + "url" + ] + }, + "result": { + "type": "object", + "properties": { + "summary": { + "type": "string", + "description": "The generated summary of the video" + } }, - "result": { - "type": "object", - "properties": { - "summary": { - "type": "string", - "description": "A markdown-formatted summary of the video content, divided into sections with timestamp links to relevant parts of the video." - } - }, - "required": [ - "summary" - ] - } - } \ No newline at end of file + "required": [ + "summary" + ] + }, + "sqlTables": [], + "sqlQueries": [], + "tools": [ + "local:::rust_toolkit:::shinkai_llm_prompt_processor" + ], + "oauth": [] +} \ No newline at end of file diff --git a/tools/youtube-summary/tool.ts b/tools/youtube-summary/tool.ts index e67ea6c2..e7677f04 100644 --- a/tools/youtube-summary/tool.ts +++ b/tools/youtube-summary/tool.ts @@ -1,22 +1,20 @@ import { YoutubeTranscript } from 'npm:youtube-transcript@1.2.1'; -import OpenAI from 'npm:openai@4.71.0'; +import { shinkaiLlmPromptProcessor } from './shinkai-local-tools.ts' -type Config = { - apiUrl?: string; - apiKey?: string; - model?: string; -}; -type Params = { +// Tool does not need any configuration +type CONFIG = Record; + +type INPUTS = { url: string; lang?: string; }; -type Result = { summary: string }; -export type Run, I extends Record, R extends Record> = (config: C, inputs: I) => Promise; -export const run: Run = async ( - configurations, +type OUTPUT = { summary: string }; + +export const run: Run = async ( + _configurations, parameters, -): Promise => { +): Promise => { console.log(`transcripting ${parameters.url}`); // Get transcription @@ -24,65 +22,22 @@ export const run: Run = async ( lang: parameters.lang || 'en', }); - // Send to ollama to build a formatted response - const message: OpenAI.ChatCompletionUserMessageParam = { - role: 'user', - content: ` - According to this transcription of a youtube video (which is in csv separated by ':::'): + // Send to LLM to build a formatted response + const message = ` + According to this transcription of a youtube video (which is in csv separated by ':::'): - offset;text - ${transcript.map((v) => `${Math.floor(v.offset)}:::${v.text}`).join('\n')} - --------------- + offset;text + ${transcript.map((v) => `${Math.floor(v.offset)}:::${v.text}`).join('\n')} + --------------- - The video URL is ${parameters.url} + The video URL is ${parameters.url} - --------------- + --------------- - Write a detailed summary divided in sections along the video. - Format the answer using markdown. - Add markdown links referencing every section using this format https://www.youtube.com/watch?v={video_id}&t={offset} where 'offset' is a number and can be obtained from the transcription in csv format to generate the URL - `, - }; - - let url = configurations?.apiUrl || 'http://127.0.0.1:11435'; - url = url?.endsWith('/v1') ? url : `${url}/v1`; - const client = new OpenAI({ - baseURL: url, - apiKey: configurations?.apiKey || '', - }); - try { - const response = await client.chat.completions.create({ - model: configurations?.model || 'llama3.1:8b-instruct-q4_1', - messages: [message], - stream: false, - }); - return Promise.resolve({ - summary: response.choices[0]?.message?.content || '', - }); - } catch (error) { - console.error('Error calling Ollama API:', error); - // Fallback to 11434 if apiUrl is not set - if (!configurations.apiUrl) { - console.log('Retrying with fallback URL http://127.0.0.1:11434'); - const fallbackClient = new OpenAI({ - baseURL: 'http://127.0.0.1:11434/v1', - apiKey: configurations?.apiKey || '', - }); - try { - const fallbackResponse = await fallbackClient.chat.completions.create({ - model: configurations?.model || 'llama3.1:8b-instruct-q4_1', - messages: [message], - stream: false, - }); - return Promise.resolve({ - summary: fallbackResponse.choices[0]?.message?.content || '', - }); - } catch (fallbackError) { - console.error('Error calling fallback Ollama API:', fallbackError); - throw fallbackError; - } - } else { - throw error; - } - } + Write a detailed summary divided in sections along the video. + Format the answer using markdown. + Add markdown links referencing every section using this format https://www.youtube.com/watch?v={video_id}&t={offset} where 'offset' is a number and can be obtained from the transcription in csv format to generate the URL + `; + const response = await shinkaiLlmPromptProcessor({ format: 'text', prompt: message }) + return { summary: response.message } };