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
12 changes: 9 additions & 3 deletions npi/app/discord/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import asyncio
import json
import os

import discord

from npi.utils import logger
from npi.core import App, npi_tool
from npi.config import config
from .schema import *
from ...error.auth import UnauthorizedError

client = discord.Client(intents=discord.Intents.default())

Expand All @@ -30,7 +31,12 @@ class Discord(App):
client: discord.Client
_access_token: str

def __init__(self, access_token: str = None, llm=None):
def __init__(self, llm=None):
cred = config.get_discord_credentials()

if cred is None:
raise UnauthorizedError("Discord credentials are not found, please use `npi auth discord` first")

super().__init__(
name='discord',
description='Send/Retrieve messages to/from discord channels',
Expand All @@ -39,7 +45,7 @@ def __init__(self, access_token: str = None, llm=None):
)

self.client = discord.Client(intents=discord.Intents.default())
self._access_token = access_token or os.environ.get('DISCORD_ACCESS_TOKEN', None)
self._access_token = cred.access_token

async def start(self):
await super().start()
Expand Down
20 changes: 9 additions & 11 deletions npi/app/github/app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import json
import os
from github import Github as PyGithub, Auth, IssueComment, PullRequestComment, Issue, PullRequest
from github.GithubObject import NotSet
from openai import OpenAI
from typing import Union, TypeVar

from npi.core import App, npi_tool
from npi.config import config
from .schema import *
from ...error.auth import UnauthorizedError

_T = TypeVar('_T')

Expand All @@ -19,22 +19,20 @@ def _default_not_set(value: _T) -> Union[_T, NotSet]:
class GitHub(App):
github_client: PyGithub

def __init__(self, access_token: str = None, llm=None):
"""
GitHub App
def __init__(self, llm=None):
cred = config.get_github_credentials()

if cred is None:
raise UnauthorizedError("GitHub credentials are not found, please use `npi auth github` first")

Args:
access_token: GitHub access token
llm: llm instance for this app, default is OpenAI
"""
super().__init__(
name='github',
description='Manage GitHub issues and pull requests using English, e.g., github("reply to the latest issue in npi/npi")',
system_role='You are a GitHub Agent helping users to manage their issues and pull requests',
llm=llm or OpenAI(),
llm=llm,
)

self.github_client = PyGithub(auth=Auth.Token(access_token or os.environ.get('GITHUB_ACCESS_TOKEN', None)))
self.github_client = PyGithub(auth=Auth.Token(cred.access_token))

@staticmethod
def _comment_to_json(comment: Union[IssueComment, PullRequestComment]):
Expand Down
6 changes: 4 additions & 2 deletions npi/app/google/calendar/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ class GoogleCalendar(GoogleApp):
def __init__(self, llm=None):
creds = config.get_google_calendar_credentials()
if creds is None:
raise UnauthorizedError("Google Calendar credentials not found, please use `npi auth google calendar` first")
raise UnauthorizedError(
"Google Calendar credentials not found, please use `npi auth google calendar` first"
)
super().__init__(
name='google_calendar',
description='a function that can invoke natural language(English only) instruction to interact with '
'Google Calendar, such as create the event, retrieve the events',
system_role='You are an assistant who are interacting with Google Calendar API. your job is the selecting '
'the best function based the tool list.',
creds=creds['token'],
creds=creds.token,
llm=llm,
scopes=self.SCOPE,
)
Expand Down
15 changes: 9 additions & 6 deletions npi/app/google/gmail/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .schema import *

from google.oauth2.credentials import Credentials
from oauth2client.client import OAuth2Credentials,GoogleCredentials
from oauth2client.client import OAuth2Credentials, GoogleCredentials


def convert_credentials(google_credentials: Credentials) -> OAuth2Credentials:
Expand All @@ -26,7 +26,8 @@ def convert_credentials(google_credentials: Credentials) -> OAuth2Credentials:
refresh_token=google_credentials._refresh_token,
token_expiry=google_credentials.expiry,
token_uri=google_credentials._token_uri,
user_agent=None)
user_agent=None
)


class Gmail(GoogleApp):
Expand All @@ -43,7 +44,7 @@ def __init__(self, llm=None):
description='interact with Gmail using English, e.g., gmail("send an email to [email protected]")',
system_role='You are a Gmail Agent helping users to manage their emails',
llm=llm,
creds=cred['token'],
creds=cred.token,
scopes=[
"https://mail.google.com/"
],
Expand Down Expand Up @@ -128,9 +129,11 @@ def remove_labels(self, params: RemoveLabelsParameters):

return 'Labels removed'

@npi_tool(description='Before sending the email, you must need to call this function for waiting user approve '
'sending email. If user asks to revise the email, you must call this function again after '
'revising.')
@npi_tool(
description='Before sending the email, you must need to call this function for waiting user approve '
'sending email. If user asks to revise the email, you must call this function again after '
'revising.'
)
async def confirm_email_sending(self, params: ConfirmEmailSendingParameters):
"""Confirm the email sending"""
loguru.logger.info(f"Waiting for user approve")
Expand Down
18 changes: 13 additions & 5 deletions npi/browser_app/twitter/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from npi.core import BrowserApp, npi_tool
from npi.utils import logger
from npi.browser_app.navigator import Navigator
from npi.config import config
from .schema import *

__SYSTEM_PROMPT__ = """
Expand Down Expand Up @@ -34,6 +35,8 @@
'home': 'https://twitter.com/home'
}

from ...error.auth import UnauthorizedError


class ImageFilterConverter(MarkdownConverter):
def process_text(self, el):
Expand Down Expand Up @@ -84,11 +87,15 @@ def html_to_md(html: str, **options) -> str:


class Twitter(BrowserApp):
# TODO: configurable credentials
secret_file: str = './credentials/twitter.json'
creds: config.TwitterCredentials
state_file: str = './credentials/twitter_state.json'

def __init__(self, llm=None, headless: bool = True):
creds = config.get_twitter_credentials()

if creds is None:
raise UnauthorizedError("Twitter credentials are not found, please use `npi auth twitter` first")

super().__init__(
name='twitter',
description='retrieve and manage tweets',
Expand All @@ -97,6 +104,8 @@ def __init__(self, llm=None, headless: bool = True):
headless=headless,
)

self.creds = creds

self.register(Navigator(playwright=self.playwright))

async def start(self):
Expand All @@ -119,12 +128,11 @@ async def _login(self):
# cookies expired, continue login process
logger.debug('Twitter cookies expired. Continue login process.')

credentials = json.load(open(self.secret_file))
await self.playwright.page.goto(__ROUTES__['login'])
await self.playwright.page.get_by_test_id('loginButton').click()
await self.playwright.page.get_by_label('Phone, email, or username').fill(credentials['username'])
await self.playwright.page.get_by_label('Phone, email, or username').fill(self.creds.username)
await self.playwright.page.get_by_role('button', name='Next').click()
await self.playwright.page.get_by_label('Password', exact=True).fill(credentials['password'])
await self.playwright.page.get_by_label('Password', exact=True).fill(self.creds.password)
await self.playwright.page.get_by_test_id('LoginForm_Login_Button').click()
await self.playwright.page.wait_for_url(__ROUTES__['home'], timeout=10_000)

Expand Down
87 changes: 65 additions & 22 deletions npi/config/config.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,80 @@
import os
from dataclasses import dataclass

CONFIG = {}


def get_project_root():
return CONFIG.get('npi_root', os.environ.get('NPI_ROOT', '/npiai'))
@dataclass(frozen=True)
class GmailCredentials:
secret: str
token: str


def get_oai_key():
return CONFIG.get('openai_api_key', os.environ.get('OPENAI_API_KEY', ''))
@dataclass(frozen=True)
class GoogleCalendarCredentials:
secret: str
token: str


def get_gmail_credentials():
if 'gmail_credentials' in CONFIG:
return CONFIG['gmail_credentials']
return None
@dataclass(frozen=True)
class GithubCredentials:
access_token: str


def set_gmail_credentials(secret, token):
CONFIG['gmail_credentials'] = {
'secret': secret,
'token': token
}
@dataclass(frozen=True)
class DiscordCredentials:
access_token: str


def get_google_calendar_credentials():
if 'google_calendar_credentials' in CONFIG:
return CONFIG['google_calendar_credentials']
return None
@dataclass(frozen=True)
class TwitterCredentials:
username: str
password: str


def set_google_calendar_credentials(secret, token):
CONFIG['google_calendar_credentials'] = {
'secret': secret,
'token': token
}
def get_project_root() -> str:
return CONFIG.get('npi_root', None) or os.environ.get('NPI_ROOT', None) or '/npiai'


def get_oai_key() -> str | None:
return CONFIG.get('openai_api_key', None) or os.environ.get('OPENAI_API_KEY', None)


def get_gmail_credentials() -> GmailCredentials | None:
return CONFIG.get('gmail_credentials', None)


def set_gmail_credentials(secret: str, token: str):
CONFIG['gmail_credentials'] = GmailCredentials(secret=secret, token=token)


def get_google_calendar_credentials() -> GoogleCalendarCredentials | None:
return CONFIG.get('google_calendar_credentials', None)


def set_google_calendar_credentials(secret: str, token: str):
CONFIG['google_calendar_credentials'] = GoogleCalendarCredentials(secret=secret, token=token)


def get_github_credentials() -> GithubCredentials | None:
return CONFIG.get('github_credentials', None)


def set_github_credentials(access_token: str):
CONFIG['github_credentials'] = GithubCredentials(access_token=access_token)


def get_discord_credentials() -> DiscordCredentials | None:
return CONFIG.get('discord_credentials', None)


def set_discord_credentials(access_token: str):
CONFIG['discord_credentials'] = DiscordCredentials(access_token=access_token)


def get_twitter_credentials() -> TwitterCredentials | None:
return CONFIG.get('twitter_credentials', None)


def set_twitter_credentials(username: str, password: str):
CONFIG['twitter_credentials'] = TwitterCredentials(username=username, password=password)
31 changes: 31 additions & 0 deletions npi/server/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ class GoogleAuthRequest(BaseModel):
app: str


class GithubAuthRequest(BaseModel):
access_token: str


class DiscordAuthRequest(BaseModel):
access_token: str


class TwitterAuthRequest(BaseModel):
username: str
password: str


STATE = {}


Expand Down Expand Up @@ -71,3 +84,21 @@ async def auth_google(state: str, code: str):
else:
config.set_gmail_credentials(cfg["secret"], credentials)
return 'Success, close the window.'


@app.post('/auth/github')
async def auth_github(req: GithubAuthRequest):
config.set_github_credentials(req.access_token)
return responses.Response(status_code=200)


@app.post('/auth/discord')
async def auth_discord(req: DiscordAuthRequest):
config.set_discord_credentials(req.access_token)
return responses.Response(status_code=200)


@app.post('/auth/twitter')
async def auth_twitter(req: TwitterAuthRequest):
config.set_twitter_credentials(req.username, req.password)
return responses.Response(status_code=200)