Common changes in all toolkits (#345)
Addresses general improvements to all toolkits including changing ruff from python 3.9 to python 3.10 which is the reason for the removal of Optional[] among others. Also, turns out that our `make install` for toolkits wasn't correctly checking for whether poetry was installed (&> /dev/null syntax isn't supported by our check-toolkits GitHub action, so we were installing poetry twice. I replaced with the more portable >/dev/null 2>&1) Question: Should we also change ruff to py310 for the `arcade/` package in a later PR? ------------------- CU-86b4gzyp6
This commit is contained in:
parent
7c6a739f25
commit
6af49ef068
84 changed files with 532 additions and 468 deletions
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ VERSION ?= "0.1.0"
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_code_sandbox"
|
||||
version = "1.0.0"
|
||||
description = "LLM tools for running code in a sandbox"
|
||||
description = "Arcade.dev LLM tools for running code in a sandbox"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -50,7 +50,7 @@ async def list_stargazers(
|
|||
owner: Annotated[str, "The owner of the repository"],
|
||||
repo: Annotated[str, "The name of the repository"],
|
||||
limit: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The maximum number of stargazers to return. "
|
||||
"If not provided, all stargazers will be returned.",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -27,12 +27,12 @@ async def create_issue(
|
|||
"The name of the repository without the .git extension. The name is not case sensitive.",
|
||||
],
|
||||
title: Annotated[str, "The title of the issue."],
|
||||
body: Annotated[Optional[str], "The contents of the issue."] = None,
|
||||
assignees: Annotated[Optional[list[str]], "Logins for Users to assign to this issue."] = None,
|
||||
body: Annotated[str | None, "The contents of the issue."] = None,
|
||||
assignees: Annotated[list[str] | None, "Logins for Users to assign to this issue."] = None,
|
||||
milestone: Annotated[
|
||||
Optional[int], "The number of the milestone to associate this issue with."
|
||||
int | None, "The number of the milestone to associate this issue with."
|
||||
] = None,
|
||||
labels: Annotated[Optional[list[str]], "Labels to associate with this issue."] = None,
|
||||
labels: Annotated[list[str] | None, "Labels to associate with this issue."] = None,
|
||||
include_extra_data: Annotated[
|
||||
bool,
|
||||
"If true, return all the data available about the pull requests. "
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -38,17 +38,17 @@ async def list_pull_requests(
|
|||
str,
|
||||
"The name of the repository without the .git extension. The name is not case sensitive.",
|
||||
],
|
||||
state: Annotated[Optional[PRState], "The state of the pull requests to return."] = PRState.OPEN,
|
||||
state: Annotated[PRState | None, "The state of the pull requests to return."] = PRState.OPEN,
|
||||
head: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"Filter pulls by head user or head organization and branch name in the format of "
|
||||
"user:ref-name or organization:ref-name.",
|
||||
] = None,
|
||||
base: Annotated[Optional[str], "Filter pulls by base branch name."] = "main",
|
||||
base: Annotated[str | None, "Filter pulls by base branch name."] = "main",
|
||||
sort: Annotated[
|
||||
Optional[PRSortProperty], "The property to sort the results by."
|
||||
PRSortProperty | None, "The property to sort the results by."
|
||||
] = PRSortProperty.CREATED,
|
||||
direction: Annotated[Optional[SortDirection], "The direction of the sort."] = None,
|
||||
direction: Annotated[SortDirection | None, "The direction of the sort."] = None,
|
||||
per_page: Annotated[int, "The number of results per page (max 100)."] = 30,
|
||||
page: Annotated[int, "The page number of the results to fetch."] = 1,
|
||||
include_extra_data: Annotated[
|
||||
|
|
@ -120,11 +120,11 @@ async def get_pull_request(
|
|||
],
|
||||
pull_number: Annotated[int, "The number that identifies the pull request."],
|
||||
include_diff_content: Annotated[
|
||||
Optional[bool],
|
||||
bool | None,
|
||||
"If true, return the diff content of the pull request.",
|
||||
] = False,
|
||||
include_extra_data: Annotated[
|
||||
Optional[bool],
|
||||
bool | None,
|
||||
"If true, return all the data available about the pull requests. "
|
||||
"This is a large payload and may impact performance - use with caution.",
|
||||
] = False,
|
||||
|
|
@ -201,16 +201,12 @@ async def update_pull_request(
|
|||
"The name of the repository without the .git extension. The name is not case sensitive.",
|
||||
],
|
||||
pull_number: Annotated[int, "The number that identifies the pull request."],
|
||||
title: Annotated[Optional[str], "The title of the pull request."] = None,
|
||||
body: Annotated[Optional[str], "The contents of the pull request."] = None,
|
||||
state: Annotated[
|
||||
Optional[PRState], "State of this Pull Request. Either open or closed."
|
||||
] = None,
|
||||
base: Annotated[
|
||||
Optional[str], "The name of the branch you want your changes pulled into."
|
||||
] = None,
|
||||
title: Annotated[str | None, "The title of the pull request."] = None,
|
||||
body: Annotated[str | None, "The contents of the pull request."] = None,
|
||||
state: Annotated[PRState | None, "State of this Pull Request. Either open or closed."] = None,
|
||||
base: Annotated[str | None, "The name of the branch you want your changes pulled into."] = None,
|
||||
maintainer_can_modify: Annotated[
|
||||
Optional[bool], "Indicates whether maintainers can modify the pull request."
|
||||
bool | None, "Indicates whether maintainers can modify the pull request."
|
||||
] = None,
|
||||
) -> Annotated[str, "JSON string containing updated information about the pull request"]:
|
||||
"""
|
||||
|
|
@ -398,14 +394,14 @@ async def list_review_comments_on_pull_request(
|
|||
],
|
||||
pull_number: Annotated[int, "The number that identifies the pull request."],
|
||||
sort: Annotated[
|
||||
Optional[ReviewCommentSortProperty],
|
||||
ReviewCommentSortProperty | None,
|
||||
"The property to sort the results by. Can be one of: created, updated.",
|
||||
] = ReviewCommentSortProperty.CREATED,
|
||||
direction: Annotated[
|
||||
Optional[SortDirection], "The direction to sort results. Can be one of: asc, desc."
|
||||
SortDirection | None, "The direction to sort results. Can be one of: asc, desc."
|
||||
] = SortDirection.DESC,
|
||||
since: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"Only show results that were last updated after the given time. "
|
||||
"This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.",
|
||||
] = None,
|
||||
|
|
@ -494,31 +490,31 @@ async def create_review_comment(
|
|||
body: Annotated[str, "The text of the review comment."],
|
||||
path: Annotated[str, "The relative path to the file that necessitates a comment."],
|
||||
commit_id: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The SHA of the commit needing a comment. If not provided, the latest commit SHA of the "
|
||||
"PR's base branch will be used.",
|
||||
] = None,
|
||||
start_line: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The start line of the range of lines in the pull request diff that the "
|
||||
"comment applies to. Required unless 'subject_type' is 'file'.",
|
||||
] = None,
|
||||
end_line: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The end line of the range of lines in the pull request diff that the "
|
||||
"comment applies to. Required unless 'subject_type' is 'file'.",
|
||||
] = None,
|
||||
side: Annotated[
|
||||
Optional[DiffSide],
|
||||
DiffSide | None,
|
||||
"The side of the diff that the pull request's changes appear on. "
|
||||
"Use LEFT for deletions that appear in red. Use RIGHT for additions that appear in green "
|
||||
"or unchanged lines that appear in white and are shown for context",
|
||||
] = DiffSide.RIGHT,
|
||||
start_side: Annotated[
|
||||
Optional[str], "The starting side of the diff that the comment applies to."
|
||||
str | None, "The starting side of the diff that the comment applies to."
|
||||
] = None,
|
||||
subject_type: Annotated[
|
||||
Optional[ReviewCommentSubjectType],
|
||||
ReviewCommentSubjectType | None,
|
||||
"The type of subject that the comment applies to. Can be one of: file, hunk, or line.",
|
||||
] = ReviewCommentSubjectType.FILE,
|
||||
include_extra_data: Annotated[
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -191,28 +191,28 @@ async def list_repository_activities(
|
|||
"The name of the repository without the .git extension. The name is not case sensitive.",
|
||||
],
|
||||
direction: Annotated[
|
||||
Optional[SortDirection], "The direction to sort the results by."
|
||||
SortDirection | None, "The direction to sort the results by."
|
||||
] = SortDirection.DESC,
|
||||
per_page: Annotated[int, "The number of results per page (max 100)."] = 30,
|
||||
before: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"A cursor (unique ID, e.g., a SHA of a commit) to search for results before this cursor.",
|
||||
] = None,
|
||||
after: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"A cursor (unique ID, e.g., a SHA of a commit) to search for results after this cursor.",
|
||||
] = None,
|
||||
ref: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The Git reference for the activities you want to list. The ref for a branch can be "
|
||||
"formatted either as refs/heads/BRANCH_NAME or BRANCH_NAME, where BRANCH_NAME is the name "
|
||||
"of your branch.",
|
||||
] = None,
|
||||
actor: Annotated[
|
||||
Optional[str], "The GitHub username to filter by the actor who performed the activity."
|
||||
str | None, "The GitHub username to filter by the actor who performed the activity."
|
||||
] = None,
|
||||
time_period: Annotated[Optional[RepoTimePeriod], "The time period to filter by."] = None,
|
||||
activity_type: Annotated[Optional[ActivityType], "The activity type to filter by."] = None,
|
||||
time_period: Annotated[RepoTimePeriod | None, "The time period to filter by."] = None,
|
||||
activity_type: Annotated[ActivityType | None, "The activity type to filter by."] = None,
|
||||
include_extra_data: Annotated[
|
||||
bool,
|
||||
"If true, return all the data available about the repository activities. "
|
||||
|
|
@ -293,14 +293,14 @@ async def list_review_comments_in_a_repository(
|
|||
"The name of the repository without the .git extension. The name is not case sensitive.",
|
||||
],
|
||||
sort: Annotated[
|
||||
Optional[ReviewCommentSortProperty], "Can be one of: created, updated."
|
||||
ReviewCommentSortProperty | None, "Can be one of: created, updated."
|
||||
] = ReviewCommentSortProperty.CREATED,
|
||||
direction: Annotated[
|
||||
Optional[SortDirection],
|
||||
SortDirection | None,
|
||||
"The direction to sort results. Ignored without sort parameter. Can be one of: asc, desc.",
|
||||
] = SortDirection.DESC,
|
||||
since: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"Only show results that were last updated after the given time. "
|
||||
"This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_github"
|
||||
version = "0.1.10"
|
||||
description = "LLM tools for interacting with Github"
|
||||
description = "Arcade.dev LLM tools for Github"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ class DateRange(Enum):
|
|||
|
||||
def to_datetime_range(
|
||||
self,
|
||||
start_time: Optional[time] = None,
|
||||
end_time: Optional[time] = None,
|
||||
time_zone: Optional[ZoneInfo] = None,
|
||||
today: Optional[date] = None,
|
||||
start_time: time | None = None,
|
||||
end_time: time | None = None,
|
||||
time_zone: ZoneInfo | None = None,
|
||||
today: date | None = None,
|
||||
) -> tuple[datetime, datetime]:
|
||||
"""
|
||||
Convert a DateRange enum value to a tuple with two datetime objects representing the start
|
||||
|
|
@ -427,10 +427,10 @@ class CellExtendedValue(BaseModel):
|
|||
Implementation of https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ExtendedValue
|
||||
"""
|
||||
|
||||
numberValue: Optional[float] = None
|
||||
stringValue: Optional[str] = None
|
||||
boolValue: Optional[bool] = None
|
||||
formulaValue: Optional[str] = None
|
||||
numberValue: float | None = None
|
||||
stringValue: str | None = None
|
||||
boolValue: bool | None = None
|
||||
formulaValue: str | None = None
|
||||
errorValue: Optional["CellErrorValue"] = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
|
|
@ -476,7 +476,7 @@ class CellData(BaseModel):
|
|||
"""
|
||||
|
||||
userEnteredValue: CellExtendedValue
|
||||
userEnteredFormat: Optional[CellFormat] = None
|
||||
userEnteredFormat: CellFormat | None = None
|
||||
|
||||
|
||||
class RowData(BaseModel):
|
||||
|
|
@ -517,7 +517,7 @@ class SheetProperties(BaseModel):
|
|||
|
||||
sheetId: int
|
||||
title: str
|
||||
gridProperties: Optional[GridProperties] = None
|
||||
gridProperties: GridProperties | None = None
|
||||
|
||||
|
||||
class Sheet(BaseModel):
|
||||
|
|
@ -527,7 +527,7 @@ class Sheet(BaseModel):
|
|||
"""
|
||||
|
||||
properties: SheetProperties
|
||||
data: Optional[list[GridData]] = None
|
||||
data: list[GridData] | None = None
|
||||
|
||||
|
||||
class SpreadsheetProperties(BaseModel):
|
||||
|
|
@ -612,7 +612,7 @@ class SheetDataInput(BaseModel):
|
|||
col_string = col_key.upper()
|
||||
if not col_string.isalpha():
|
||||
raise TypeError(f"Column key '{col_key}' is invalid. Must be alphabetic.")
|
||||
if not isinstance(cell_value, (int, float, str, bool)):
|
||||
if not isinstance(cell_value, int | float | str | bool):
|
||||
raise TypeError(
|
||||
f"Cell value for {col_string}{row_int} must be an int, float, str, or bool."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -373,18 +373,18 @@ async def delete_event(
|
|||
async def find_time_slots_when_everyone_is_free(
|
||||
context: ToolContext,
|
||||
email_addresses: Annotated[
|
||||
Optional[list[str]],
|
||||
list[str] | None,
|
||||
"The list of email addresses from people in the same organization domain (apart from the "
|
||||
"currently logged in user) to search for free time slots. Defaults to None, which will "
|
||||
"return free time slots for the current user only.",
|
||||
] = None,
|
||||
start_date: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The start date to search for time slots in the format 'YYYY-MM-DD'. Defaults to today's "
|
||||
"date. It will search starting from this date at the time 00:00:00.",
|
||||
] = None,
|
||||
end_date: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The end date to search for time slots in the format 'YYYY-MM-DD'. Defaults to seven days "
|
||||
"from the start date. It will search until this date at the time 23:59:59.",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import asyncio
|
||||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Google
|
||||
|
|
@ -25,7 +25,7 @@ async def search_contacts_by_email(
|
|||
context: ToolContext,
|
||||
email: Annotated[str, "The email address to search for"],
|
||||
limit: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The maximum number of contacts to return (30 is the max allowed by Google API)",
|
||||
] = DEFAULT_SEARCH_CONTACTS_LIMIT,
|
||||
) -> Annotated[dict, "A dictionary containing the list of matching contacts"]:
|
||||
|
|
@ -47,7 +47,7 @@ async def search_contacts_by_name(
|
|||
context: ToolContext,
|
||||
name: Annotated[str, "The full name to search for"],
|
||||
limit: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The maximum number of contacts to return (30 is the max allowed by Google API)",
|
||||
] = DEFAULT_SEARCH_CONTACTS_LIMIT,
|
||||
) -> Annotated[dict, "A dictionary containing the list of matching contacts"]:
|
||||
|
|
@ -67,8 +67,8 @@ async def search_contacts_by_name(
|
|||
async def create_contact(
|
||||
context: ToolContext,
|
||||
given_name: Annotated[str, "The given name of the contact"],
|
||||
family_name: Annotated[Optional[str], "The optional family name of the contact"],
|
||||
email: Annotated[Optional[str], "The optional email address of the contact"],
|
||||
family_name: Annotated[str | None, "The optional family name of the contact"],
|
||||
email: Annotated[str | None, "The optional email address of the contact"],
|
||||
) -> Annotated[dict, "A dictionary containing the details of the created contact"]:
|
||||
"""
|
||||
Create a new contact record in Google Contacts.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Google
|
||||
|
|
@ -27,7 +27,7 @@ async def get_file_tree_structure(
|
|||
bool, "Whether to include shared drives in the file tree structure. Defaults to False."
|
||||
] = False,
|
||||
restrict_to_shared_drive_id: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"If provided, only include files from this shared drive in the file tree structure. "
|
||||
"Defaults to None, which will include files and folders from all drives.",
|
||||
] = None,
|
||||
|
|
@ -38,11 +38,11 @@ async def get_file_tree_structure(
|
|||
"account. Defaults to False.",
|
||||
] = False,
|
||||
order_by: Annotated[
|
||||
Optional[list[OrderBy]],
|
||||
list[OrderBy] | None,
|
||||
"Sort order. Defaults to listing the most recently modified documents first",
|
||||
] = None,
|
||||
limit: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The number of files and folders to list. Defaults to None, "
|
||||
"which will list all files and folders.",
|
||||
] = None,
|
||||
|
|
@ -121,17 +121,17 @@ async def get_file_tree_structure(
|
|||
async def search_documents(
|
||||
context: ToolContext,
|
||||
document_contains: Annotated[
|
||||
Optional[list[str]],
|
||||
list[str] | None,
|
||||
"Keywords or phrases that must be in the document title or body. Provide a list of "
|
||||
"keywords or phrases if needed.",
|
||||
] = None,
|
||||
document_not_contains: Annotated[
|
||||
Optional[list[str]],
|
||||
list[str] | None,
|
||||
"Keywords or phrases that must NOT be in the document title or body. Provide a list of "
|
||||
"keywords or phrases if needed.",
|
||||
] = None,
|
||||
search_only_in_shared_drive_id: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The ID of the shared drive to restrict the search to. If provided, the search will only "
|
||||
"return documents from this drive. Defaults to None, which searches across all drives.",
|
||||
] = None,
|
||||
|
|
@ -147,12 +147,12 @@ async def search_documents(
|
|||
"account. Defaults to False.",
|
||||
] = False,
|
||||
order_by: Annotated[
|
||||
Optional[list[OrderBy]],
|
||||
list[OrderBy] | None,
|
||||
"Sort order. Defaults to listing the most recently modified documents first",
|
||||
] = None,
|
||||
limit: Annotated[int, "The number of documents to list"] = 50,
|
||||
pagination_token: Annotated[
|
||||
Optional[str], "The pagination token to continue a previous request"
|
||||
str | None, "The pagination token to continue a previous request"
|
||||
] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
|
|
@ -215,17 +215,17 @@ async def search_and_retrieve_documents(
|
|||
"The format of the document to return. Defaults to Markdown.",
|
||||
] = DocumentFormat.MARKDOWN,
|
||||
document_contains: Annotated[
|
||||
Optional[list[str]],
|
||||
list[str] | None,
|
||||
"Keywords or phrases that must be in the document title or body. Provide a list of "
|
||||
"keywords or phrases if needed.",
|
||||
] = None,
|
||||
document_not_contains: Annotated[
|
||||
Optional[list[str]],
|
||||
list[str] | None,
|
||||
"Keywords or phrases that must NOT be in the document title or body. Provide a list of "
|
||||
"keywords or phrases if needed.",
|
||||
] = None,
|
||||
search_only_in_shared_drive_id: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The ID of the shared drive to restrict the search to. If provided, the search will only "
|
||||
"return documents from this drive. Defaults to None, which searches across all drives.",
|
||||
] = None,
|
||||
|
|
@ -241,12 +241,12 @@ async def search_and_retrieve_documents(
|
|||
"account. Defaults to False.",
|
||||
] = False,
|
||||
order_by: Annotated[
|
||||
Optional[list[OrderBy]],
|
||||
list[OrderBy] | None,
|
||||
"Sort order. Defaults to listing the most recently modified documents first",
|
||||
] = None,
|
||||
limit: Annotated[int, "The number of documents to list"] = 50,
|
||||
pagination_token: Annotated[
|
||||
Optional[str], "The pagination token to continue a previous request"
|
||||
str | None, "The pagination token to continue a previous request"
|
||||
] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import base64
|
||||
from email.mime.text import MIMEText
|
||||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Google
|
||||
|
|
@ -40,8 +40,8 @@ async def send_email(
|
|||
subject: Annotated[str, "The subject of the email"],
|
||||
body: Annotated[str, "The body of the email"],
|
||||
recipient: Annotated[str, "The recipient of the email"],
|
||||
cc: Annotated[Optional[list[str]], "CC recipients of the email"] = None,
|
||||
bcc: Annotated[Optional[list[str]], "BCC recipients of the email"] = None,
|
||||
cc: Annotated[list[str] | None, "CC recipients of the email"] = None,
|
||||
bcc: Annotated[list[str] | None, "BCC recipients of the email"] = None,
|
||||
) -> Annotated[dict, "A dictionary containing the sent email details"]:
|
||||
"""
|
||||
Send an email using the Gmail API.
|
||||
|
|
@ -96,7 +96,7 @@ async def reply_to_email(
|
|||
"Whether to reply to every recipient (including cc) or only to the original sender. "
|
||||
f"Defaults to '{GMAIL_DEFAULT_REPLY_TO}'.",
|
||||
] = GMAIL_DEFAULT_REPLY_TO,
|
||||
bcc: Annotated[Optional[list[str]], "BCC recipients of the email"] = None,
|
||||
bcc: Annotated[list[str] | None, "BCC recipients of the email"] = None,
|
||||
) -> Annotated[dict, "A dictionary containing the sent email details"]:
|
||||
"""
|
||||
Send a reply to an email message.
|
||||
|
|
@ -156,8 +156,8 @@ async def write_draft_email(
|
|||
subject: Annotated[str, "The subject of the draft email"],
|
||||
body: Annotated[str, "The body of the draft email"],
|
||||
recipient: Annotated[str, "The recipient of the draft email"],
|
||||
cc: Annotated[Optional[list[str]], "CC recipients of the draft email"] = None,
|
||||
bcc: Annotated[Optional[list[str]], "BCC recipients of the draft email"] = None,
|
||||
cc: Annotated[list[str] | None, "CC recipients of the draft email"] = None,
|
||||
bcc: Annotated[list[str] | None, "BCC recipients of the draft email"] = None,
|
||||
) -> Annotated[dict, "A dictionary containing the created draft email details"]:
|
||||
"""
|
||||
Compose a new email draft using the Gmail API.
|
||||
|
|
@ -193,7 +193,7 @@ async def write_draft_reply_email(
|
|||
"Whether to reply to every recipient (including cc) or only to the original sender. "
|
||||
f"Defaults to '{GMAIL_DEFAULT_REPLY_TO}'.",
|
||||
] = GMAIL_DEFAULT_REPLY_TO,
|
||||
bcc: Annotated[Optional[list[str]], "BCC recipients of the draft reply email"] = None,
|
||||
bcc: Annotated[list[str] | None, "BCC recipients of the draft reply email"] = None,
|
||||
) -> Annotated[dict, "A dictionary containing the created draft reply email details"]:
|
||||
"""
|
||||
Compose a draft reply to an email message.
|
||||
|
|
@ -256,8 +256,8 @@ async def update_draft_email(
|
|||
subject: Annotated[str, "The subject of the draft email"],
|
||||
body: Annotated[str, "The body of the draft email"],
|
||||
recipient: Annotated[str, "The recipient of the draft email"],
|
||||
cc: Annotated[Optional[list[str]], "CC recipients of the draft email"] = None,
|
||||
bcc: Annotated[Optional[list[str]], "BCC recipients of the draft email"] = None,
|
||||
cc: Annotated[list[str] | None, "CC recipients of the draft email"] = None,
|
||||
bcc: Annotated[list[str] | None, "BCC recipients of the draft email"] = None,
|
||||
) -> Annotated[dict, "A dictionary containing the updated draft email details"]:
|
||||
"""
|
||||
Update an existing email draft using the Gmail API.
|
||||
|
|
@ -374,12 +374,12 @@ async def list_draft_emails(
|
|||
)
|
||||
async def list_emails_by_header(
|
||||
context: ToolContext,
|
||||
sender: Annotated[Optional[str], "The name or email address of the sender of the email"] = None,
|
||||
recipient: Annotated[Optional[str], "The name or email address of the recipient"] = None,
|
||||
subject: Annotated[Optional[str], "Words to find in the subject of the email"] = None,
|
||||
body: Annotated[Optional[str], "Words to find in the body of the email"] = None,
|
||||
date_range: Annotated[Optional[DateRange], "The date range of the email"] = None,
|
||||
label: Annotated[Optional[str], "The label name to filter by"] = None,
|
||||
sender: Annotated[str | None, "The name or email address of the sender of the email"] = None,
|
||||
recipient: Annotated[str | None, "The name or email address of the recipient"] = None,
|
||||
subject: Annotated[str | None, "Words to find in the subject of the email"] = None,
|
||||
body: Annotated[str | None, "Words to find in the body of the email"] = None,
|
||||
date_range: Annotated[DateRange | None, "The date range of the email"] = None,
|
||||
label: Annotated[str | None, "The label name to filter by"] = None,
|
||||
max_results: Annotated[int, "The maximum number of emails to return"] = 25,
|
||||
) -> Annotated[
|
||||
dict, "A dictionary containing a list of email details matching the search criteria"
|
||||
|
|
@ -475,16 +475,16 @@ async def list_emails(
|
|||
async def search_threads(
|
||||
context: ToolContext,
|
||||
page_token: Annotated[
|
||||
Optional[str], "Page token to retrieve a specific page of results in the list"
|
||||
str | None, "Page token to retrieve a specific page of results in the list"
|
||||
] = None,
|
||||
max_results: Annotated[int, "The maximum number of threads to return"] = 10,
|
||||
include_spam_trash: Annotated[bool, "Whether to include spam and trash in the results"] = False,
|
||||
label_ids: Annotated[Optional[list[str]], "The IDs of labels to filter by"] = None,
|
||||
sender: Annotated[Optional[str], "The name or email address of the sender of the email"] = None,
|
||||
recipient: Annotated[Optional[str], "The name or email address of the recipient"] = None,
|
||||
subject: Annotated[Optional[str], "Words to find in the subject of the email"] = None,
|
||||
body: Annotated[Optional[str], "Words to find in the body of the email"] = None,
|
||||
date_range: Annotated[Optional[DateRange], "The date range of the email"] = None,
|
||||
label_ids: Annotated[list[str] | None, "The IDs of labels to filter by"] = None,
|
||||
sender: Annotated[str | None, "The name or email address of the sender of the email"] = None,
|
||||
recipient: Annotated[str | None, "The name or email address of the recipient"] = None,
|
||||
subject: Annotated[str | None, "Words to find in the subject of the email"] = None,
|
||||
body: Annotated[str | None, "Words to find in the body of the email"] = None,
|
||||
date_range: Annotated[DateRange | None, "The date range of the email"] = None,
|
||||
) -> Annotated[dict, "A dictionary containing a list of thread details"]:
|
||||
"""Search for threads in the user's mailbox"""
|
||||
service = _build_gmail_service(context)
|
||||
|
|
@ -535,7 +535,7 @@ async def search_threads(
|
|||
async def list_threads(
|
||||
context: ToolContext,
|
||||
page_token: Annotated[
|
||||
Optional[str], "Page token to retrieve a specific page of results in the list"
|
||||
str | None, "Page token to retrieve a specific page of results in the list"
|
||||
] = None,
|
||||
max_results: Annotated[int, "The maximum number of threads to return"] = 10,
|
||||
include_spam_trash: Annotated[bool, "Whether to include spam and trash in the results"] = False,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Google
|
||||
|
|
@ -27,7 +27,7 @@ def create_spreadsheet(
|
|||
context: ToolContext,
|
||||
title: Annotated[str, "The title of the new spreadsheet"] = "Untitled spreadsheet",
|
||||
data: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The data to write to the spreadsheet. A JSON string "
|
||||
"(property names enclosed in double quotes) representing a dictionary that "
|
||||
"maps row numbers to dictionaries that map column letters to cell values. "
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from datetime import date, datetime, time, timedelta, timezone
|
|||
from email.message import EmailMessage
|
||||
from email.mime.text import MIMEText
|
||||
from enum import Enum
|
||||
from typing import Any, Optional, Union, cast
|
||||
from typing import Any, cast
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from arcade.sdk import ToolContext
|
||||
|
|
@ -114,15 +114,15 @@ def build_email_message(
|
|||
recipient: str,
|
||||
subject: str,
|
||||
body: str,
|
||||
cc: Optional[list[str]] = None,
|
||||
bcc: Optional[list[str]] = None,
|
||||
replying_to: Optional[dict[str, Any]] = None,
|
||||
cc: list[str] | None = None,
|
||||
bcc: list[str] | None = None,
|
||||
replying_to: dict[str, Any] | None = None,
|
||||
action: GmailAction = GmailAction.SEND,
|
||||
) -> dict[str, Any]:
|
||||
if replying_to:
|
||||
body = build_reply_body(body, replying_to)
|
||||
|
||||
message: Union[EmailMessage, MIMEText]
|
||||
message: EmailMessage | MIMEText
|
||||
|
||||
if action == GmailAction.SEND:
|
||||
message = EmailMessage()
|
||||
|
|
@ -183,10 +183,10 @@ def parse_plain_text_email(email_data: dict[str, Any]) -> dict[str, Any]:
|
|||
Only returns the plain text body.
|
||||
|
||||
Args:
|
||||
email_data (Dict[str, Any]): Raw email data from Gmail API.
|
||||
email_data (dict[str, Any]): Raw email data from Gmail API.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, str]]: Parsed email details or None if parsing fails.
|
||||
dict[str, str]: Parsed email details
|
||||
"""
|
||||
payload = email_data.get("payload", {})
|
||||
headers = {d["name"].lower(): d["value"] for d in payload.get("headers", [])}
|
||||
|
|
@ -223,7 +223,7 @@ def parse_multipart_email(email_data: dict[str, Any]) -> dict[str, Any]:
|
|||
email_data (Dict[str, Any]): Raw email data from Gmail API.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: Parsed email details or None if parsing fails.
|
||||
dict[str, Any]: Parsed email details
|
||||
"""
|
||||
|
||||
payload = email_data.get("payload", {})
|
||||
|
|
@ -263,7 +263,7 @@ def parse_draft_email(draft_email_data: dict[str, Any]) -> dict[str, str]:
|
|||
draft_email_data (Dict[str, Any]): Raw draft email data from Gmail API.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, str]]: Parsed draft email details or None if parsing fails.
|
||||
dict[str, str]: Parsed draft email details
|
||||
"""
|
||||
message = draft_email_data.get("message", {})
|
||||
payload = message.get("payload", {})
|
||||
|
|
@ -342,7 +342,7 @@ def _build_gmail_service(context: ToolContext) -> Any:
|
|||
return build("gmail", "v1", credentials=credentials)
|
||||
|
||||
|
||||
def _extract_plain_body(parts: list) -> Optional[str]:
|
||||
def _extract_plain_body(parts: list) -> str | None:
|
||||
"""
|
||||
Recursively extract the email body from parts, handling both plain text and HTML.
|
||||
|
||||
|
|
@ -350,7 +350,7 @@ def _extract_plain_body(parts: list) -> Optional[str]:
|
|||
parts (List[Dict[str, Any]]): List of email parts.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Decoded and cleaned email body or None if not found.
|
||||
str | None: Decoded and cleaned email body or None if not found.
|
||||
"""
|
||||
for part in parts:
|
||||
mime_type = part.get("mimeType")
|
||||
|
|
@ -367,7 +367,7 @@ def _extract_plain_body(parts: list) -> Optional[str]:
|
|||
return _extract_html_body(parts)
|
||||
|
||||
|
||||
def _extract_html_body(parts: list) -> Optional[str]:
|
||||
def _extract_html_body(parts: list) -> str | None:
|
||||
"""
|
||||
Recursively extract the email body from parts, handling only HTML.
|
||||
|
||||
|
|
@ -375,7 +375,7 @@ def _extract_html_body(parts: list) -> Optional[str]:
|
|||
parts (List[Dict[str, Any]]): List of email parts.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Decoded and cleaned email body or None if not found.
|
||||
str | None: Decoded and cleaned email body or None if not found.
|
||||
"""
|
||||
for part in parts:
|
||||
mime_type = part.get("mimeType")
|
||||
|
|
@ -393,7 +393,7 @@ def _extract_html_body(parts: list) -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def _get_email_images(payload: dict[str, Any]) -> Optional[list[str]]:
|
||||
def _get_email_images(payload: dict[str, Any]) -> list[str] | None:
|
||||
"""
|
||||
Extract the email images from an email payload.
|
||||
|
||||
|
|
@ -401,7 +401,7 @@ def _get_email_images(payload: dict[str, Any]) -> Optional[list[str]]:
|
|||
payload (Dict[str, Any]): Email payload data.
|
||||
|
||||
Returns:
|
||||
Optional[List[str]]: List of decoded image contents or None if none found.
|
||||
list[str] | None: List of decoded image contents or None if none found.
|
||||
"""
|
||||
images = []
|
||||
for part in payload.get("parts", []):
|
||||
|
|
@ -423,7 +423,7 @@ def _get_email_images(payload: dict[str, Any]) -> Optional[list[str]]:
|
|||
return None
|
||||
|
||||
|
||||
def _get_email_plain_text_body(payload: dict[str, Any]) -> Optional[str]:
|
||||
def _get_email_plain_text_body(payload: dict[str, Any]) -> str | None:
|
||||
"""
|
||||
Extract email body from payload, handling 'multipart/alternative' parts.
|
||||
|
||||
|
|
@ -431,7 +431,7 @@ def _get_email_plain_text_body(payload: dict[str, Any]) -> Optional[str]:
|
|||
payload (Dict[str, Any]): Email payload data.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Decoded email body or None if not found.
|
||||
str | None: Decoded email body or None if not found.
|
||||
"""
|
||||
# Direct body extraction
|
||||
if "body" in payload and payload["body"].get("data"):
|
||||
|
|
@ -441,7 +441,7 @@ def _get_email_plain_text_body(payload: dict[str, Any]) -> Optional[str]:
|
|||
return _clean_email_body(_extract_plain_body(payload.get("parts", [])))
|
||||
|
||||
|
||||
def _get_email_html_body(payload: dict[str, Any]) -> Optional[str]:
|
||||
def _get_email_html_body(payload: dict[str, Any]) -> str | None:
|
||||
"""
|
||||
Extract email html body from payload, handling 'multipart/alternative' parts.
|
||||
|
||||
|
|
@ -449,7 +449,7 @@ def _get_email_html_body(payload: dict[str, Any]) -> Optional[str]:
|
|||
payload (Dict[str, Any]): Email payload data.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Decoded email body or None if not found.
|
||||
str | None: Decoded email body or None if not found.
|
||||
"""
|
||||
# Direct body extraction
|
||||
if "body" in payload and payload["body"].get("data"):
|
||||
|
|
@ -459,7 +459,7 @@ def _get_email_html_body(payload: dict[str, Any]) -> Optional[str]:
|
|||
return _extract_html_body(payload.get("parts", []))
|
||||
|
||||
|
||||
def _clean_email_body(body: Optional[str]) -> str:
|
||||
def _clean_email_body(body: str | None) -> str:
|
||||
"""
|
||||
Remove HTML tags and clean up email body text while preserving most content.
|
||||
|
||||
|
|
@ -608,7 +608,7 @@ def remove_none_values(params: dict) -> dict:
|
|||
|
||||
|
||||
# Drive utils
|
||||
def build_drive_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported]
|
||||
def build_drive_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
|
||||
"""
|
||||
Build a Drive service object.
|
||||
"""
|
||||
|
|
@ -618,8 +618,8 @@ def build_drive_service(auth_token: Optional[str]) -> Resource: # type: ignore[
|
|||
|
||||
def build_files_list_query(
|
||||
mime_type: str,
|
||||
document_contains: Optional[list[str]] = None,
|
||||
document_not_contains: Optional[list[str]] = None,
|
||||
document_contains: list[str] | None = None,
|
||||
document_not_contains: list[str] | None = None,
|
||||
) -> str:
|
||||
query = [f"(mimeType = '{mime_type}' and trashed = false)"]
|
||||
|
||||
|
|
@ -655,12 +655,12 @@ def build_files_list_params(
|
|||
mime_type: str,
|
||||
page_size: int,
|
||||
order_by: list[OrderBy],
|
||||
pagination_token: Optional[str],
|
||||
pagination_token: str | None,
|
||||
include_shared_drives: bool,
|
||||
search_only_in_shared_drive_id: Optional[str],
|
||||
search_only_in_shared_drive_id: str | None,
|
||||
include_organization_domain_documents: bool,
|
||||
document_contains: Optional[list[str]] = None,
|
||||
document_not_contains: Optional[list[str]] = None,
|
||||
document_contains: list[str] | None = None,
|
||||
document_not_contains: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
query = build_files_list_query(
|
||||
mime_type=mime_type,
|
||||
|
|
@ -696,11 +696,11 @@ def build_files_list_params(
|
|||
|
||||
|
||||
def build_file_tree_request_params(
|
||||
order_by: Optional[list[OrderBy]],
|
||||
page_token: Optional[str],
|
||||
limit: Optional[int],
|
||||
order_by: list[OrderBy] | None,
|
||||
page_token: str | None,
|
||||
limit: int | None,
|
||||
include_shared_drives: bool,
|
||||
restrict_to_shared_drive_id: Optional[str],
|
||||
restrict_to_shared_drive_id: str | None,
|
||||
include_organization_domain_documents: bool,
|
||||
) -> dict[str, Any]:
|
||||
if order_by is None:
|
||||
|
|
@ -787,7 +787,7 @@ def build_file_tree(files: dict[str, Any]) -> dict[str, Any]:
|
|||
|
||||
|
||||
# Docs utils
|
||||
def build_docs_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported]
|
||||
def build_docs_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
|
||||
"""
|
||||
Build a Drive service object.
|
||||
"""
|
||||
|
|
@ -989,7 +989,7 @@ def get_now(tz: ZoneInfo | None = None) -> datetime:
|
|||
|
||||
|
||||
# Contacts utils
|
||||
def build_people_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported]
|
||||
def build_people_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
|
||||
"""
|
||||
Build a People service object.
|
||||
"""
|
||||
|
|
@ -997,7 +997,7 @@ def build_people_service(auth_token: Optional[str]) -> Resource: # type: ignore
|
|||
return build("people", "v1", credentials=Credentials(auth_token))
|
||||
|
||||
|
||||
def search_contacts(service: Any, query: str, limit: Optional[int]) -> list[dict[str, Any]]:
|
||||
def search_contacts(service: Any, query: str, limit: int | None) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Search the user's contacts in Google Contacts.
|
||||
"""
|
||||
|
|
@ -1029,7 +1029,7 @@ def search_contacts(service: Any, query: str, limit: Optional[int]) -> list[dict
|
|||
# ----------------------------------------------------------------
|
||||
|
||||
|
||||
def build_sheets_service(auth_token: Optional[str]) -> Resource: # type: ignore[no-any-unimported]
|
||||
def build_sheets_service(auth_token: str | None) -> Resource: # type: ignore[no-any-unimported]
|
||||
"""
|
||||
Build a Sheets service object.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_google"
|
||||
version = "1.1.0"
|
||||
description = "Arcade tools for the entire google suite"
|
||||
description = "Arcade.dev LLM tools for Google Workspace"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -26,7 +26,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ def test_create_row_data():
|
|||
row_data = create_row_data(row_data, min_col_index, max_col_index)
|
||||
|
||||
assert len(row_data.values) == len(expected_row_data.values)
|
||||
for cell, expected in zip(row_data.values, expected_row_data.values):
|
||||
for cell, expected in zip(row_data.values, expected_row_data.values, strict=False):
|
||||
assert cell.userEnteredValue == expected.userEnteredValue
|
||||
|
||||
|
||||
|
|
@ -268,7 +268,7 @@ def test_create_sheet_data():
|
|||
create_cell_data("row2B"),
|
||||
create_cell_data(200),
|
||||
]
|
||||
for cell, expected in zip(row2_cells, expected_row2):
|
||||
for cell, expected in zip(row2_cells, expected_row2, strict=False):
|
||||
assert cell.userEnteredValue == expected.userEnteredValue
|
||||
|
||||
row3_cells = group1.rowData[1].values
|
||||
|
|
@ -277,7 +277,7 @@ def test_create_sheet_data():
|
|||
create_cell_data("row3B"),
|
||||
CellData(userEnteredValue=CellExtendedValue(stringValue="")),
|
||||
]
|
||||
for cell, expected in zip(row3_cells, expected_row3):
|
||||
for cell, expected in zip(row3_cells, expected_row3, strict=False):
|
||||
assert cell.userEnteredValue == expected.userEnteredValue
|
||||
|
||||
group2 = grid_data_list[1]
|
||||
|
|
@ -291,7 +291,7 @@ def test_create_sheet_data():
|
|||
CellData(userEnteredValue=CellExtendedValue(stringValue="")),
|
||||
create_cell_data("row5C"),
|
||||
]
|
||||
for cell, expected in zip(row5_cells, expected_row5):
|
||||
for cell, expected in zip(row5_cells, expected_row5, strict=False):
|
||||
assert cell.userEnteredValue == expected.userEnteredValue
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_linkedin"
|
||||
version = "0.1.10"
|
||||
description = "Arcade tools for LinkedIn"
|
||||
description = "Arcade.dev LLM tools for LinkedIn"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import random
|
||||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import tool
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ def generate_random_int(
|
|||
min_value: Annotated[str, "The minimum value of the random integer as a string"],
|
||||
max_value: Annotated[str, "The maximum value of the random integer as a string"],
|
||||
seed: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The seed for the random number generator as a string."
|
||||
" If None, the current system time is used.",
|
||||
] = None,
|
||||
|
|
@ -26,7 +26,7 @@ def generate_random_float(
|
|||
min_value: Annotated[str, "The minimum value of the random float as a string"],
|
||||
max_value: Annotated[str, "The maximum value of the random float as a string"],
|
||||
seed: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The seed for the random number generator as a string."
|
||||
" If None, the current system time is used.",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_math"
|
||||
version = "1.0.1"
|
||||
description = "Math toolkit for Arcade"
|
||||
description = "Arcade.dev LLM tools for doing math"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -19,7 +19,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import asyncio
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from arcade.sdk import ToolContext
|
||||
|
||||
|
|
@ -100,14 +100,14 @@ class BlockToMarkdownConverter:
|
|||
return md
|
||||
|
||||
@staticmethod
|
||||
def apply_formatting(text: str, annotations: dict[str, Any], link: Optional[str] = None) -> str:
|
||||
def apply_formatting(text: str, annotations: dict[str, Any], link: str | None = None) -> str:
|
||||
"""Apply formatting to a text string based on the annotations.
|
||||
Used when converting rich text to markdown
|
||||
|
||||
Args:
|
||||
text (str): The text to format.
|
||||
annotations (dict[str, Any]): The annotations to apply to the text.
|
||||
link (Optional[str]): An optional link for a hyperlink.
|
||||
link (str | None): An optional link for a hyperlink.
|
||||
|
||||
Returns:
|
||||
str: The formatted text.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -95,7 +95,7 @@ async def create_page(
|
|||
"Title of an existing page/database within which the new page will be created. ",
|
||||
],
|
||||
title: Annotated[str, "Title of the new page"],
|
||||
content: Annotated[Optional[str], "The content of the new page"] = None,
|
||||
content: Annotated[str | None, "The content of the new page"] = None,
|
||||
) -> Annotated[str, "The ID of the new page"]:
|
||||
"""Create a new Notion page by the title of the new page's parent."""
|
||||
# Notion API does not support creating a page at the root of the workspace... sigh
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -19,12 +19,12 @@ from arcade_notion_toolkit.utils import (
|
|||
async def search_by_title(
|
||||
context: ToolContext,
|
||||
query: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"A substring to search for within page and database titles. "
|
||||
"If not provided (default), all pages and/or databases are returned.",
|
||||
] = None,
|
||||
select: Annotated[
|
||||
Optional[ObjectType],
|
||||
ObjectType | None,
|
||||
"Limit the results to either only pages or only databases. Defaults to both.",
|
||||
] = None,
|
||||
order_by: Annotated[
|
||||
|
|
@ -87,11 +87,11 @@ async def search_by_title(
|
|||
async def get_object_metadata(
|
||||
context: ToolContext,
|
||||
object_title: Annotated[
|
||||
Optional[str], "Title of the page or database whose metadata to get"
|
||||
str | None, "Title of the page or database whose metadata to get"
|
||||
] = None,
|
||||
object_id: Annotated[Optional[str], "ID of the page or database whose metadata to get"] = None,
|
||||
object_id: Annotated[str | None, "ID of the page or database whose metadata to get"] = None,
|
||||
object_type: Annotated[
|
||||
Optional[ObjectType],
|
||||
ObjectType | None,
|
||||
"The type of object to match title to. Only used if `object_title` is provided. "
|
||||
"Defaults to both",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext
|
||||
|
|
@ -118,8 +118,8 @@ async def get_next_page(
|
|||
client: httpx.AsyncClient,
|
||||
url: str,
|
||||
headers: dict,
|
||||
params: Optional[dict] = None,
|
||||
cursor: Optional[str] = None,
|
||||
params: dict | None = None,
|
||||
cursor: str | None = None,
|
||||
) -> tuple[dict, bool, str]:
|
||||
"""
|
||||
Retrieves the next page of results from a Notion API endpoint.
|
||||
|
|
@ -129,8 +129,8 @@ async def get_next_page(
|
|||
client (httpx.AsyncClient): The HTTP client to use for the request.
|
||||
url (str): The URL of the endpoint to request.
|
||||
headers (dict): The headers to use for the request.
|
||||
params (Optional[dict]): The parameters to use for the request.
|
||||
cursor (Optional[str]): The cursor to use for the request.
|
||||
params (dict | None): The parameters to use for the request.
|
||||
cursor (str | None): The cursor to use for the request.
|
||||
|
||||
Returns:
|
||||
tuple[dict, bool, str]: A tuple containing the results, a boolean indicating if there is a
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_notion_toolkit"
|
||||
version = "0.1.1"
|
||||
description = "LLM tools for essential Notion interactions such as creating, updating, retrieving, and searching pages."
|
||||
description = "Arcade.dev LLM tools for Notion"
|
||||
authors = ["ArcadeAI <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -17,7 +17,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Reddit
|
||||
|
|
@ -33,7 +33,7 @@ async def get_posts_in_subreddit(
|
|||
),
|
||||
] = SubredditListingType.HOT,
|
||||
limit: Annotated[int, "The maximum number of posts to fetch. Default is 10, max is 100."] = 10,
|
||||
cursor: Annotated[Optional[str], "The pagination token from a previous call"] = None,
|
||||
cursor: Annotated[str | None, "The pagination token from a previous call"] = None,
|
||||
time_range: Annotated[
|
||||
RedditTimeFilter,
|
||||
"The time range for filtering posts. Must be provided if the listing type is "
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Reddit
|
||||
|
|
@ -19,20 +19,20 @@ async def submit_text_post(
|
|||
subreddit: Annotated[str, "The name of the subreddit to which the post will be submitted"],
|
||||
title: Annotated[str, "The title of the submission"],
|
||||
body: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The body of the post in markdown format. Should never be the same as the title",
|
||||
] = None,
|
||||
nsfw: Annotated[
|
||||
Optional[bool],
|
||||
bool | None,
|
||||
"Indicates if the submission has content that is 'Not Safe For Work' (NSFW). "
|
||||
"Default is False",
|
||||
] = False,
|
||||
spoiler: Annotated[
|
||||
Optional[bool],
|
||||
bool | None,
|
||||
"Indicates if the post is marked as a spoiler. Default is False",
|
||||
] = False,
|
||||
send_replies: Annotated[
|
||||
Optional[bool], "If true, sends replies to the user's inbox. Default is True"
|
||||
bool | None, "If true, sends replies to the user's inbox. Default is True"
|
||||
] = True,
|
||||
) -> Annotated[dict, "Response from Reddit after submission"]:
|
||||
"""Submit a text-based post to a subreddit"""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_reddit"
|
||||
version = "0.0.1"
|
||||
description = "LLM tools for interacting with Reddit"
|
||||
description = "Arcade.dev LLM tools Reddit"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -17,7 +17,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
|
@ -138,7 +137,7 @@ class WalmartSortBy(Enum):
|
|||
RATING_HIGH = "highest_rating_first"
|
||||
NEW_ARRIVALS = "new_arrivals_first"
|
||||
|
||||
def to_api_value(self: "WalmartSortBy") -> Optional[str]:
|
||||
def to_api_value(self: "WalmartSortBy") -> str | None:
|
||||
_map = {
|
||||
str(self.RELEVANCE): None,
|
||||
str(self.PRICE_LOW_TO_HIGH): "price_low",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import json
|
||||
from typing import Optional
|
||||
|
||||
from arcade.sdk.errors import RetryableToolError
|
||||
|
||||
|
|
@ -11,7 +10,7 @@ class GoogleRetryableError(RetryableToolError):
|
|||
|
||||
|
||||
class CountryNotFoundError(GoogleRetryableError):
|
||||
def __init__(self, country: Optional[str]) -> None:
|
||||
def __init__(self, country: str | None) -> None:
|
||||
valid_countries = json.dumps(COUNTRY_CODES, default=str)
|
||||
message = f"Country not found: '{country}'."
|
||||
additional_message = f"Valid countries are: {valid_countries}"
|
||||
|
|
@ -19,7 +18,7 @@ class CountryNotFoundError(GoogleRetryableError):
|
|||
|
||||
|
||||
class LanguageNotFoundError(GoogleRetryableError):
|
||||
def __init__(self, language: Optional[str]) -> None:
|
||||
def __init__(self, language: str | None) -> None:
|
||||
valid_languages = json.dumps(LANGUAGE_CODES, default=str)
|
||||
message = f"Language not found: '{language}'."
|
||||
additional_message = f"Valid languages are: {valid_languages}"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
||||
|
|
@ -14,16 +14,16 @@ async def search_roundtrip_flights(
|
|||
],
|
||||
arrival_airport_code: Annotated[str, "The arrival airport code. An uppercase 3-letter code"],
|
||||
outbound_date: Annotated[str, "Flight outbound date in YYYY-MM-DD format"],
|
||||
return_date: Annotated[Optional[str], "Flight return date in YYYY-MM-DD format"],
|
||||
return_date: Annotated[str | None, "Flight return date in YYYY-MM-DD format"],
|
||||
currency_code: Annotated[
|
||||
Optional[str], "Currency of the returned prices. Defaults to 'USD'"
|
||||
str | None, "Currency of the returned prices. Defaults to 'USD'"
|
||||
] = "USD",
|
||||
travel_class: Annotated[
|
||||
GoogleFlightsTravelClass,
|
||||
"Travel class of the flight. Defaults to 'ECONOMY'",
|
||||
] = GoogleFlightsTravelClass.ECONOMY,
|
||||
num_adults: Annotated[Optional[int], "Number of adult passengers. Defaults to 1"] = 1,
|
||||
num_children: Annotated[Optional[int], "Number of child passengers. Defaults to 0"] = 0,
|
||||
num_adults: Annotated[int | None, "Number of adult passengers. Defaults to 1"] = 1,
|
||||
num_children: Annotated[int | None, "Number of child passengers. Defaults to 0"] = 0,
|
||||
max_stops: Annotated[
|
||||
GoogleFlightsMaxStops,
|
||||
"Maximum number of stops (layovers) for the flight. Defaults to any number of stops",
|
||||
|
|
@ -68,14 +68,14 @@ async def search_one_way_flights(
|
|||
arrival_airport_code: Annotated[str, "The arrival airport code. An uppercase 3-letter code"],
|
||||
outbound_date: Annotated[str, "Flight departure date in YYYY-MM-DD format"],
|
||||
currency_code: Annotated[
|
||||
Optional[str], "Currency of the returned prices. Defaults to 'USD'"
|
||||
str | None, "Currency of the returned prices. Defaults to 'USD'"
|
||||
] = "USD",
|
||||
travel_class: Annotated[
|
||||
GoogleFlightsTravelClass,
|
||||
"Travel class of the flight. Defaults to 'ECONOMY'",
|
||||
] = GoogleFlightsTravelClass.ECONOMY,
|
||||
num_adults: Annotated[Optional[int], "Number of adult passengers. Defaults to 1"] = 1,
|
||||
num_children: Annotated[Optional[int], "Number of child passengers. Defaults to 0"] = 0,
|
||||
num_adults: Annotated[int | None, "Number of adult passengers. Defaults to 1"] = 1,
|
||||
num_children: Annotated[int | None, "Number of child passengers. Defaults to 0"] = 0,
|
||||
max_stops: Annotated[
|
||||
GoogleFlightsMaxStops,
|
||||
"Maximum number of stops (layovers) for the flight. Defaults to any number of stops",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
||||
|
|
@ -13,18 +13,18 @@ async def search_hotels(
|
|||
check_in_date: Annotated[str, "Check-in date in YYYY-MM-DD format"],
|
||||
check_out_date: Annotated[str, "Check-out date in YYYY-MM-DD format"],
|
||||
query: Annotated[
|
||||
Optional[str], "Anything that would be used in a regular Google Hotels search"
|
||||
str | None, "Anything that would be used in a regular Google Hotels search"
|
||||
] = None,
|
||||
currency: Annotated[Optional[str], "Currency code for prices. Defaults to 'USD'"] = "USD",
|
||||
min_price: Annotated[Optional[int], "Minimum price per night. Defaults to no minimum"] = None,
|
||||
max_price: Annotated[Optional[int], "Maximum price per night. Defaults to no maximum"] = None,
|
||||
num_adults: Annotated[Optional[int], "Number of adults per room. Defaults to 2"] = 2,
|
||||
num_children: Annotated[Optional[int], "Number of children per room. Defaults to 0"] = 0,
|
||||
currency: Annotated[str | None, "Currency code for prices. Defaults to 'USD'"] = "USD",
|
||||
min_price: Annotated[int | None, "Minimum price per night. Defaults to no minimum"] = None,
|
||||
max_price: Annotated[int | None, "Maximum price per night. Defaults to no maximum"] = None,
|
||||
num_adults: Annotated[int | None, "Number of adults per room. Defaults to 2"] = 2,
|
||||
num_children: Annotated[int | None, "Number of children per room. Defaults to 0"] = 0,
|
||||
sort_by: Annotated[
|
||||
GoogleHotelsSortBy, "The sorting order of the results. Defaults to RELEVANCE"
|
||||
] = GoogleHotelsSortBy.RELEVANCE,
|
||||
num_results: Annotated[
|
||||
Optional[int], "Maximum number of results to return. Defaults to 5. Max 20"
|
||||
int | None, "Maximum number of results to return. Defaults to 5. Max 20"
|
||||
] = 5,
|
||||
) -> Annotated[dict[str, Any], "Hotel search results from the Google Hotels API"]:
|
||||
"""Retrieve hotel search results using the Google Hotels API."""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ async def search_jobs(
|
|||
"or 'data analyst at Apple'.",
|
||||
],
|
||||
location: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"Location to search for jobs. E.g. 'United States' or 'New York, NY'. Defaults to None.",
|
||||
] = None,
|
||||
language: Annotated[
|
||||
|
|
@ -31,7 +31,7 @@ async def search_jobs(
|
|||
"Maximum number of results to retrieve. Defaults to 10 (max supported by the API).",
|
||||
] = 10,
|
||||
next_page_token: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"Next page token to paginate results. Defaults to None (start from the first page).",
|
||||
] = None,
|
||||
) -> Annotated[dict, "Google Jobs results"]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ async def get_directions_between_addresses(
|
|||
f"Defaults to '{DEFAULT_GOOGLE_MAPS_LANGUAGE}'.",
|
||||
] = DEFAULT_GOOGLE_MAPS_LANGUAGE,
|
||||
country: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character country code to use in the Google Maps search. "
|
||||
f"Defaults to '{DEFAULT_GOOGLE_MAPS_COUNTRY}'.",
|
||||
] = DEFAULT_GOOGLE_MAPS_COUNTRY,
|
||||
|
|
@ -69,7 +69,7 @@ async def get_directions_between_coordinates(
|
|||
f"Defaults to '{DEFAULT_GOOGLE_MAPS_LANGUAGE}'.",
|
||||
] = DEFAULT_GOOGLE_MAPS_LANGUAGE,
|
||||
country: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
f"2-letter country code to use in the Google Maps search. Defaults to "
|
||||
f"'{DEFAULT_GOOGLE_MAPS_COUNTRY}'.",
|
||||
] = DEFAULT_GOOGLE_MAPS_COUNTRY,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.errors import ToolExecutionError
|
||||
|
|
@ -17,7 +17,7 @@ async def search_news_stories(
|
|||
"Keywords to search for news articles. E.g. 'Apple launches new iPhone'.",
|
||||
],
|
||||
country_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character country code to search for news articles. E.g. 'us' (United States). "
|
||||
f"Defaults to '{DEFAULT_GOOGLE_NEWS_COUNTRY}'.",
|
||||
] = None,
|
||||
|
|
@ -27,7 +27,7 @@ async def search_news_stories(
|
|||
f"Defaults to '{DEFAULT_GOOGLE_NEWS_LANGUAGE}'.",
|
||||
] = DEFAULT_GOOGLE_NEWS_LANGUAGE,
|
||||
limit: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"Maximum number of news articles to return. Defaults to None "
|
||||
"(returns all results found by the API).",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.errors import ToolExecutionError
|
||||
|
|
@ -25,12 +25,12 @@ async def search_shopping_products(
|
|||
"Keywords to search for products in Google Shopping. E.g. 'Apple iPhone'.",
|
||||
],
|
||||
country_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character country code to search for products in Google Shopping. "
|
||||
f"E.g. 'us' (United States). Defaults to '{DEFAULT_GOOGLE_SHOPPING_COUNTRY or 'us'}'.",
|
||||
] = DEFAULT_GOOGLE_SHOPPING_COUNTRY,
|
||||
language_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character language code to search for products on Google Shopping. E.g. 'en' (English). "
|
||||
f"Defaults to '{DEFAULT_GOOGLE_SHOPPING_LANGUAGE or 'en'}'.",
|
||||
] = DEFAULT_GOOGLE_SHOPPING_LANGUAGE,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext
|
||||
from arcade.sdk.errors import ToolExecutionError
|
||||
|
|
@ -24,11 +24,11 @@ async def search_walmart_products(
|
|||
f"Defaults to '{WalmartSortBy.RELEVANCE.value}'.",
|
||||
] = WalmartSortBy.RELEVANCE,
|
||||
min_price: Annotated[
|
||||
Optional[float],
|
||||
float | None,
|
||||
"Minimum price to filter the results by. E.g. 100.00",
|
||||
] = None,
|
||||
max_price: Annotated[
|
||||
Optional[float],
|
||||
float | None,
|
||||
"Maximum price to filter the results by. E.g. 100.00",
|
||||
] = None,
|
||||
next_day_delivery: Annotated[
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional, cast
|
||||
from typing import Annotated, Any, cast
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.errors import ToolExecutionError
|
||||
|
|
@ -24,17 +24,17 @@ async def search_youtube_videos(
|
|||
"The keywords to search for. E.g. 'Python tutorial'.",
|
||||
],
|
||||
language_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character language code to search for. E.g. 'en' for English. "
|
||||
f"Defaults to '{default_language_code(DEFAULT_YOUTUBE_SEARCH_LANGUAGE)}'.",
|
||||
] = None,
|
||||
country_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character country code to search for. E.g. 'us' for United States. "
|
||||
f"Defaults to '{default_country_code(DEFAULT_YOUTUBE_SEARCH_COUNTRY)}'.",
|
||||
] = None,
|
||||
next_page_token: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The next page token to use for pagination. "
|
||||
"Defaults to `None` (start from the first page).",
|
||||
] = None,
|
||||
|
|
@ -70,12 +70,12 @@ async def get_youtube_video_details(
|
|||
"The ID of the YouTube video to get details about. E.g. 'dQw4w9WgXcQ'.",
|
||||
],
|
||||
language_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character language code to search for. E.g. 'en' for English. "
|
||||
f"Defaults to '{default_language_code(DEFAULT_YOUTUBE_SEARCH_LANGUAGE)}'.",
|
||||
] = None,
|
||||
country_code: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"2-character country code to search for. E.g. 'us' for United States. "
|
||||
f"Defaults to '{default_country_code(DEFAULT_YOUTUBE_SEARCH_COUNTRY)}'.",
|
||||
] = None,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import contextlib
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional, cast
|
||||
from typing import Any, cast
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ def call_serpapi(context: ToolContext, params: dict) -> dict:
|
|||
# ------------------------------------------------------------------------------------------------
|
||||
# Google general utils
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
def default_language_code(default_service_language_code: Optional[str] = None) -> Optional[str]:
|
||||
def default_language_code(default_service_language_code: str | None = None) -> str | None:
|
||||
if isinstance(default_service_language_code, str):
|
||||
return default_service_language_code.lower()
|
||||
elif isinstance(DEFAULT_GOOGLE_LANGUAGE, str):
|
||||
|
|
@ -79,7 +79,7 @@ def default_language_code(default_service_language_code: Optional[str] = None) -
|
|||
return None
|
||||
|
||||
|
||||
def default_country_code(default_service_country_code: Optional[str] = None) -> Optional[str]:
|
||||
def default_country_code(default_service_country_code: str | None = None) -> str | None:
|
||||
if isinstance(default_service_country_code, str):
|
||||
return default_service_country_code.lower()
|
||||
elif isinstance(DEFAULT_GOOGLE_COUNTRY, str):
|
||||
|
|
@ -88,9 +88,9 @@ def default_country_code(default_service_country_code: Optional[str] = None) ->
|
|||
|
||||
|
||||
def resolve_language_code(
|
||||
language_code: Optional[str] = None,
|
||||
default_service_language_code: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
language_code: str | None = None,
|
||||
default_service_language_code: str | None = None,
|
||||
) -> str | None:
|
||||
language_code = language_code or default_language_code(default_service_language_code)
|
||||
|
||||
if isinstance(language_code, str):
|
||||
|
|
@ -102,9 +102,9 @@ def resolve_language_code(
|
|||
|
||||
|
||||
def resolve_country_code(
|
||||
country_code: Optional[str] = None,
|
||||
default_service_country_code: Optional[str] = None,
|
||||
) -> Optional[str]:
|
||||
country_code: str | None = None,
|
||||
default_service_country_code: str | None = None,
|
||||
) -> str | None:
|
||||
country_code = country_code or default_country_code(default_service_country_code)
|
||||
|
||||
if isinstance(country_code, str):
|
||||
|
|
@ -120,14 +120,14 @@ def resolve_country_code(
|
|||
# ------------------------------------------------------------------------------------------------
|
||||
def get_google_maps_directions(
|
||||
context: ToolContext,
|
||||
origin_address: Optional[str] = None,
|
||||
destination_address: Optional[str] = None,
|
||||
origin_latitude: Optional[str] = None,
|
||||
origin_longitude: Optional[str] = None,
|
||||
destination_latitude: Optional[str] = None,
|
||||
destination_longitude: Optional[str] = None,
|
||||
language: Optional[str] = DEFAULT_GOOGLE_MAPS_LANGUAGE,
|
||||
country: Optional[str] = DEFAULT_GOOGLE_MAPS_COUNTRY,
|
||||
origin_address: str | None = None,
|
||||
destination_address: str | None = None,
|
||||
origin_latitude: str | None = None,
|
||||
origin_longitude: str | None = None,
|
||||
destination_latitude: str | None = None,
|
||||
destination_longitude: str | None = None,
|
||||
language: str | None = DEFAULT_GOOGLE_MAPS_LANGUAGE,
|
||||
country: str | None = DEFAULT_GOOGLE_MAPS_COUNTRY,
|
||||
distance_unit: GoogleMapsDistanceUnit = DEFAULT_GOOGLE_MAPS_DISTANCE_UNIT,
|
||||
travel_mode: GoogleMapsTravelMode = DEFAULT_GOOGLE_MAPS_TRAVEL_MODE,
|
||||
) -> list[dict[str, Any]]:
|
||||
|
|
@ -224,7 +224,7 @@ def clean_google_maps_direction(direction: dict[str, Any]) -> None:
|
|||
del stop["data_id"]
|
||||
|
||||
|
||||
def enrich_google_maps_arrive_around(timestamp: Optional[int]) -> dict[str, Any]:
|
||||
def enrich_google_maps_arrive_around(timestamp: int | None) -> dict[str, Any]:
|
||||
if not timestamp:
|
||||
return {}
|
||||
|
||||
|
|
@ -258,9 +258,7 @@ def parse_flight_results(results: dict[str, Any]) -> dict[str, Any]:
|
|||
# ------------------------------------------------------------------------------------------------
|
||||
# Google News utils
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
def extract_news_results(
|
||||
results: dict[str, Any], limit: Optional[int] = None
|
||||
) -> list[dict[str, Any]]:
|
||||
def extract_news_results(results: dict[str, Any], limit: int | None = None) -> list[dict[str, Any]]:
|
||||
news_results = []
|
||||
for result in results.get("news_results", []):
|
||||
news_results.append({
|
||||
|
|
@ -375,7 +373,7 @@ def extract_walmart_variant_options(variant_swatches: list[dict[str, Any]]) -> l
|
|||
# ------------------------------------------------------------------------------------------------
|
||||
# YouTube utils
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
def extract_video_id_from_link(link: Optional[str]) -> Optional[str]:
|
||||
def extract_video_id_from_link(link: str | None) -> str | None:
|
||||
if not isinstance(link, str):
|
||||
return None
|
||||
|
||||
|
|
@ -387,7 +385,7 @@ def extract_video_id_from_link(link: Optional[str]) -> Optional[str]:
|
|||
def extract_video_description(
|
||||
video: dict[str, Any],
|
||||
max_description_length: int = YOUTUBE_MAX_DESCRIPTION_LENGTH,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
description = video.get("description", "")
|
||||
|
||||
if isinstance(description, dict):
|
||||
|
|
@ -401,7 +399,7 @@ def extract_video_description(
|
|||
if description is not None:
|
||||
description = str(description).strip()
|
||||
|
||||
return cast(Optional[str], description)
|
||||
return cast(str | None, description)
|
||||
|
||||
|
||||
def extract_video_results(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_search"
|
||||
version = "1.4.0"
|
||||
description = "Tools for searching the web"
|
||||
description = "Arcade.dev LLM tools for searching the web"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from typing import NewType, Optional, Union
|
||||
from typing import NewType
|
||||
|
||||
|
||||
class PositiveInt(int):
|
||||
def __new__(cls, value: Union[str, int], name: str = "value") -> "PositiveInt":
|
||||
def __new__(cls, value: str | int, name: str = "value") -> "PositiveInt":
|
||||
def validate(val: int) -> int:
|
||||
if val <= 0:
|
||||
raise ValueError(f"{name} must be positive, got {val}")
|
||||
|
|
@ -19,7 +19,7 @@ class PositiveInt(int):
|
|||
|
||||
|
||||
SlackOffsetSecondsFromUTC = NewType("SlackOffsetSecondsFromUTC", int) # observe it can be negative
|
||||
SlackPaginationNextCursor = Optional[str]
|
||||
SlackPaginationNextCursor = str | None
|
||||
SlackUserFieldId = NewType("SlackUserFieldId", str)
|
||||
SlackUserId = NewType("SlackUserId", str)
|
||||
SlackTeamId = NewType("SlackTeamId", str)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from enum import Enum
|
||||
from typing import Literal, Optional, TypedDict
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
from arcade_slack.custom_types import (
|
||||
SlackOffsetSecondsFromUTC,
|
||||
|
|
@ -45,15 +45,15 @@ class SlackUserFieldData(TypedDict, total=False):
|
|||
Slack type definition: https://api.slack.com/methods/users.profile.set#custom-profile
|
||||
"""
|
||||
|
||||
value: Optional[str]
|
||||
alt: Optional[bool]
|
||||
value: str | None
|
||||
alt: bool | None
|
||||
|
||||
|
||||
class SlackStatusEmojiDisplayInfo(TypedDict, total=False):
|
||||
"""Type definition for Slack status emoji display info dictionary."""
|
||||
|
||||
emoji_name: Optional[str]
|
||||
display_url: Optional[str]
|
||||
emoji_name: str | None
|
||||
display_url: str | None
|
||||
|
||||
|
||||
class SlackUserProfile(TypedDict, total=False):
|
||||
|
|
@ -62,37 +62,37 @@ class SlackUserProfile(TypedDict, total=False):
|
|||
Slack type definition: https://api.slack.com/types/user#profile (https://archive.is/RUZdL)
|
||||
"""
|
||||
|
||||
title: Optional[str]
|
||||
phone: Optional[str]
|
||||
skype: Optional[str]
|
||||
email: Optional[str]
|
||||
real_name: Optional[str]
|
||||
real_name_normalized: Optional[str]
|
||||
display_name: Optional[str]
|
||||
display_name_normalized: Optional[str]
|
||||
first_name: Optional[str]
|
||||
last_name: Optional[str]
|
||||
fields: Optional[list[dict[SlackUserFieldId, SlackUserFieldData]]]
|
||||
image_original: Optional[str]
|
||||
is_custom_image: Optional[bool]
|
||||
image_24: Optional[str]
|
||||
image_32: Optional[str]
|
||||
image_48: Optional[str]
|
||||
image_72: Optional[str]
|
||||
image_192: Optional[str]
|
||||
image_512: Optional[str]
|
||||
image_1024: Optional[str]
|
||||
status_emoji: Optional[str]
|
||||
status_emoji_display_info: Optional[list[SlackStatusEmojiDisplayInfo]]
|
||||
status_text: Optional[str]
|
||||
status_text_canonical: Optional[str]
|
||||
status_expiration: Optional[int]
|
||||
avatar_hash: Optional[str]
|
||||
start_date: Optional[str]
|
||||
pronouns: Optional[str]
|
||||
huddle_state: Optional[str]
|
||||
huddle_state_expiration: Optional[int]
|
||||
team: Optional[SlackTeamId]
|
||||
title: str | None
|
||||
phone: str | None
|
||||
skype: str | None
|
||||
email: str | None
|
||||
real_name: str | None
|
||||
real_name_normalized: str | None
|
||||
display_name: str | None
|
||||
display_name_normalized: str | None
|
||||
first_name: str | None
|
||||
last_name: str | None
|
||||
fields: list[dict[SlackUserFieldId, SlackUserFieldData]] | None
|
||||
image_original: str | None
|
||||
is_custom_image: bool | None
|
||||
image_24: str | None
|
||||
image_32: str | None
|
||||
image_48: str | None
|
||||
image_72: str | None
|
||||
image_192: str | None
|
||||
image_512: str | None
|
||||
image_1024: str | None
|
||||
status_emoji: str | None
|
||||
status_emoji_display_info: list[SlackStatusEmojiDisplayInfo] | None
|
||||
status_text: str | None
|
||||
status_text_canonical: str | None
|
||||
status_expiration: int | None
|
||||
avatar_hash: str | None
|
||||
start_date: str | None
|
||||
pronouns: str | None
|
||||
huddle_state: str | None
|
||||
huddle_state_expiration: int | None
|
||||
team: SlackTeamId | None
|
||||
|
||||
|
||||
class SlackUser(TypedDict, total=False):
|
||||
|
|
@ -103,23 +103,23 @@ class SlackUser(TypedDict, total=False):
|
|||
|
||||
id: SlackUserId
|
||||
team_id: SlackTeamId
|
||||
name: Optional[str]
|
||||
deleted: Optional[bool]
|
||||
color: Optional[str]
|
||||
real_name: Optional[str]
|
||||
tz: Optional[str]
|
||||
tz_label: Optional[str]
|
||||
tz_offset: Optional[SlackOffsetSecondsFromUTC]
|
||||
profile: Optional[SlackUserProfile]
|
||||
is_admin: Optional[bool]
|
||||
is_owner: Optional[bool]
|
||||
is_primary_owner: Optional[bool]
|
||||
is_restricted: Optional[bool]
|
||||
is_ultra_restricted: Optional[bool]
|
||||
is_bot: Optional[bool]
|
||||
is_app_user: Optional[bool]
|
||||
is_email_confirmed: Optional[bool]
|
||||
who_can_share_contact_card: Optional[str]
|
||||
name: str | None
|
||||
deleted: bool | None
|
||||
color: str | None
|
||||
real_name: str | None
|
||||
tz: str | None
|
||||
tz_label: str | None
|
||||
tz_offset: SlackOffsetSecondsFromUTC | None
|
||||
profile: SlackUserProfile | None
|
||||
is_admin: bool | None
|
||||
is_owner: bool | None
|
||||
is_primary_owner: bool | None
|
||||
is_restricted: bool | None
|
||||
is_ultra_restricted: bool | None
|
||||
is_bot: bool | None
|
||||
is_app_user: bool | None
|
||||
is_email_confirmed: bool | None
|
||||
who_can_share_contact_card: str | None
|
||||
|
||||
|
||||
class SlackUserList(TypedDict, total=False):
|
||||
|
|
@ -131,25 +131,25 @@ class SlackUserList(TypedDict, total=False):
|
|||
class SlackConversationPurpose(TypedDict, total=False):
|
||||
"""Type definition for the Slack conversation purpose dictionary."""
|
||||
|
||||
value: Optional[str]
|
||||
value: str | None
|
||||
|
||||
|
||||
class SlackConversation(TypedDict, total=False):
|
||||
"""Type definition for the Slack conversation dictionary."""
|
||||
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
is_private: Optional[bool]
|
||||
is_archived: Optional[bool]
|
||||
is_member: Optional[bool]
|
||||
is_channel: Optional[bool]
|
||||
is_group: Optional[bool]
|
||||
is_im: Optional[bool]
|
||||
is_mpim: Optional[bool]
|
||||
purpose: Optional[SlackConversationPurpose]
|
||||
num_members: Optional[int]
|
||||
user: Optional[SlackUser]
|
||||
is_user_deleted: Optional[bool]
|
||||
id: str | None
|
||||
name: str | None
|
||||
is_private: bool | None
|
||||
is_archived: bool | None
|
||||
is_member: bool | None
|
||||
is_channel: bool | None
|
||||
is_group: bool | None
|
||||
is_im: bool | None
|
||||
is_mpim: bool | None
|
||||
purpose: SlackConversationPurpose | None
|
||||
num_members: int | None
|
||||
user: SlackUser | None
|
||||
is_user_deleted: bool | None
|
||||
|
||||
|
||||
class SlackMessage(TypedDict, total=True):
|
||||
|
|
@ -175,28 +175,28 @@ class Message(SlackMessage, total=False):
|
|||
class ConversationMetadata(TypedDict, total=False):
|
||||
"""Type definition for the conversation metadata dictionary."""
|
||||
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
conversation_type: Optional[str]
|
||||
is_private: Optional[bool]
|
||||
is_archived: Optional[bool]
|
||||
is_member: Optional[bool]
|
||||
purpose: Optional[str]
|
||||
num_members: Optional[int]
|
||||
user: Optional[SlackUser]
|
||||
is_user_deleted: Optional[bool]
|
||||
id: str | None
|
||||
name: str | None
|
||||
conversation_type: str | None
|
||||
is_private: bool | None
|
||||
is_archived: bool | None
|
||||
is_member: bool | None
|
||||
purpose: str | None
|
||||
num_members: int | None
|
||||
user: SlackUser | None
|
||||
is_user_deleted: bool | None
|
||||
|
||||
|
||||
class BasicUserInfo(TypedDict, total=False):
|
||||
"""Type definition for the returned basic user info dictionary."""
|
||||
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
is_bot: Optional[bool]
|
||||
email: Optional[str]
|
||||
display_name: Optional[str]
|
||||
real_name: Optional[str]
|
||||
timezone: Optional[str]
|
||||
id: str | None
|
||||
name: str | None
|
||||
is_bot: bool | None
|
||||
email: str | None
|
||||
display_name: str | None
|
||||
real_name: str | None
|
||||
timezone: str | None
|
||||
|
||||
|
||||
class SlackConversationsToolResponse(TypedDict, total=True):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
from typing import Annotated, Optional, cast
|
||||
from typing import Annotated, cast
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Slack
|
||||
|
|
@ -149,8 +149,8 @@ async def send_message_to_channel(
|
|||
async def get_members_in_conversation_by_id(
|
||||
context: ToolContext,
|
||||
conversation_id: Annotated[str, "The ID of the conversation to get members for"],
|
||||
limit: Annotated[Optional[int], "The maximum number of members to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of members to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[dict, "Information about each member in the conversation"]:
|
||||
"""Get the members of a conversation in Slack by the conversation's ID."""
|
||||
token = (
|
||||
|
|
@ -209,8 +209,8 @@ async def get_members_in_conversation_by_id(
|
|||
async def get_members_in_channel_by_name(
|
||||
context: ToolContext,
|
||||
channel_name: Annotated[str, "The name of the channel to get members for"],
|
||||
limit: Annotated[Optional[int], "The maximum number of members to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of members to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[dict, "The channel members' IDs and Names"]:
|
||||
"""Get the members of a conversation in Slack by the conversation's name."""
|
||||
channel = await get_channel_metadata_by_name(context=context, channel_name=channel_name)
|
||||
|
|
@ -235,35 +235,35 @@ async def get_messages_in_conversation_by_id(
|
|||
context: ToolContext,
|
||||
conversation_id: Annotated[str, "The ID of the conversation to get history for"],
|
||||
oldest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
latest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
oldest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
latest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
limit: Annotated[Optional[int], "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
(
|
||||
|
|
@ -367,35 +367,35 @@ async def get_messages_in_channel_by_name(
|
|||
context: ToolContext,
|
||||
channel_name: Annotated[str, "The name of the channel"],
|
||||
oldest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
latest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
oldest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
latest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
limit: Annotated[Optional[int], "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
(
|
||||
|
|
@ -438,35 +438,35 @@ async def get_messages_in_direct_message_conversation_by_username(
|
|||
context: ToolContext,
|
||||
username: Annotated[str, "The username of the user to get messages from"],
|
||||
oldest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
latest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
oldest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
latest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
limit: Annotated[Optional[int], "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
(
|
||||
|
|
@ -511,35 +511,35 @@ async def get_messages_in_multi_person_dm_conversation_by_usernames(
|
|||
context: ToolContext,
|
||||
usernames: Annotated[list[str], "The usernames of the users to get messages from"],
|
||||
oldest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
latest_relative: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a time offset from the "
|
||||
"current time in the format 'DD:HH:MM'"
|
||||
),
|
||||
] = None,
|
||||
oldest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The oldest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
latest_datetime: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
(
|
||||
"The latest message to include in the results, specified as a datetime object in the "
|
||||
"format 'YYYY-MM-DD HH:MM:SS'"
|
||||
),
|
||||
] = None,
|
||||
limit: Annotated[Optional[int], "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of messages to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
(
|
||||
|
|
@ -629,7 +629,7 @@ async def get_channel_metadata_by_name(
|
|||
context: ToolContext,
|
||||
channel_name: Annotated[str, "The name of the channel to get metadata for"],
|
||||
next_cursor: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The cursor to use for pagination, if continuing from a previous search.",
|
||||
] = None,
|
||||
) -> Annotated[dict, "The channel metadata"]:
|
||||
|
|
@ -698,11 +698,11 @@ async def get_direct_message_conversation_metadata_by_username(
|
|||
context: ToolContext,
|
||||
username: Annotated[str, "The username of the user/person to get messages with"],
|
||||
next_cursor: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The cursor to use for pagination, if continuing from a previous search.",
|
||||
] = None,
|
||||
) -> Annotated[
|
||||
Optional[dict],
|
||||
dict | None,
|
||||
"The direct message conversation metadata.",
|
||||
]:
|
||||
"""Get the metadata of a direct message conversation in Slack by the username.
|
||||
|
|
@ -750,11 +750,11 @@ async def get_multi_person_dm_conversation_metadata_by_usernames(
|
|||
context: ToolContext,
|
||||
usernames: Annotated[list[str], "The usernames of the users/people to get messages with"],
|
||||
next_cursor: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The cursor to use for pagination, if continuing from a previous search.",
|
||||
] = None,
|
||||
) -> Annotated[
|
||||
Optional[dict],
|
||||
dict | None,
|
||||
"The multi-person direct message conversation metadata.",
|
||||
]:
|
||||
"""Get the metadata of a multi-person direct message conversation in Slack by the usernames.
|
||||
|
|
@ -818,11 +818,11 @@ async def get_multi_person_dm_conversation_metadata_by_usernames(
|
|||
async def list_conversations_metadata(
|
||||
context: ToolContext,
|
||||
conversation_types: Annotated[
|
||||
Optional[list[ConversationType]],
|
||||
list[ConversationType] | None,
|
||||
"The type(s) of conversations to list. Defaults to all types.",
|
||||
] = None,
|
||||
limit: Annotated[Optional[int], "The maximum number of conversations to list."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The cursor to use for pagination."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of conversations to list."] = None,
|
||||
next_cursor: Annotated[str | None, "The cursor to use for pagination."] = None,
|
||||
) -> Annotated[
|
||||
dict,
|
||||
(
|
||||
|
|
@ -873,7 +873,7 @@ async def list_conversations_metadata(
|
|||
)
|
||||
async def list_public_channels_metadata(
|
||||
context: ToolContext,
|
||||
limit: Annotated[Optional[int], "The maximum number of channels to list."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of channels to list."] = None,
|
||||
) -> Annotated[dict, "The public channels"]:
|
||||
"""List metadata for public channels in Slack that the user is a member of."""
|
||||
|
||||
|
|
@ -891,7 +891,7 @@ async def list_public_channels_metadata(
|
|||
)
|
||||
async def list_private_channels_metadata(
|
||||
context: ToolContext,
|
||||
limit: Annotated[Optional[int], "The maximum number of channels to list."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of channels to list."] = None,
|
||||
) -> Annotated[dict, "The private channels"]:
|
||||
"""List metadata for private channels in Slack that the user is a member of."""
|
||||
|
||||
|
|
@ -909,7 +909,7 @@ async def list_private_channels_metadata(
|
|||
)
|
||||
async def list_group_direct_message_conversations_metadata(
|
||||
context: ToolContext,
|
||||
limit: Annotated[Optional[int], "The maximum number of conversations to list."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of conversations to list."] = None,
|
||||
) -> Annotated[dict, "The group direct message conversations metadata"]:
|
||||
"""List metadata for group direct message conversations that the user is a member of."""
|
||||
|
||||
|
|
@ -929,7 +929,7 @@ async def list_group_direct_message_conversations_metadata(
|
|||
)
|
||||
async def list_direct_message_conversations_metadata(
|
||||
context: ToolContext,
|
||||
limit: Annotated[Optional[int], "The maximum number of conversations to list."] = None,
|
||||
limit: Annotated[int | None, "The maximum number of conversations to list."] = None,
|
||||
) -> Annotated[dict, "The direct message conversations metadata"]:
|
||||
"""List metadata for direct message conversations in Slack that the user is a member of."""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional, cast
|
||||
from typing import Annotated, Any, cast
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Slack
|
||||
|
|
@ -59,9 +59,9 @@ async def get_user_info_by_id(
|
|||
)
|
||||
async def list_users(
|
||||
context: ToolContext,
|
||||
exclude_bots: Annotated[Optional[bool], "Whether to exclude bots from the results"] = True,
|
||||
limit: Annotated[Optional[int], "The maximum number of users to return."] = None,
|
||||
next_cursor: Annotated[Optional[str], "The next cursor token to use for pagination."] = None,
|
||||
exclude_bots: Annotated[bool | None, "Whether to exclude bots from the results"] = True,
|
||||
limit: Annotated[int | None, "The maximum number of users to return."] = None,
|
||||
next_cursor: Annotated[str | None, "The next cursor token to use for pagination."] = None,
|
||||
) -> Annotated[dict, "The users' info"]:
|
||||
"""List all users in the authenticated user's Slack team."""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Callable, Optional
|
||||
from typing import Any
|
||||
|
||||
from arcade.sdk import ToolContext
|
||||
from arcade.sdk.errors import RetryableToolError
|
||||
|
|
@ -137,7 +138,7 @@ def extract_conversation_metadata(conversation: SlackConversation) -> Conversati
|
|||
"""
|
||||
conversation_type = get_slack_conversation_type_as_str(conversation)
|
||||
|
||||
purpose: Optional[SlackConversationPurpose] = conversation.get("purpose")
|
||||
purpose: SlackConversationPurpose | None = conversation.get("purpose")
|
||||
purpose_value = "" if not purpose else purpose.get("value", "")
|
||||
|
||||
metadata = ConversationMetadata(
|
||||
|
|
@ -219,8 +220,8 @@ async def retrieve_conversations_by_user_ids(
|
|||
conversation_types: list[ConversationType],
|
||||
user_ids: list[str],
|
||||
exact_match: bool = False,
|
||||
limit: Optional[int] = None,
|
||||
next_cursor: Optional[str] = None,
|
||||
limit: int | None = None,
|
||||
next_cursor: str | None = None,
|
||||
) -> list[dict]:
|
||||
"""
|
||||
Retrieve conversations filtered by the given user IDs. Includes pagination support
|
||||
|
|
@ -312,13 +313,13 @@ def is_user_deleted(user: SlackUser) -> bool:
|
|||
|
||||
async def async_paginate(
|
||||
func: Callable,
|
||||
response_key: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
next_cursor: Optional[SlackPaginationNextCursor] = None,
|
||||
response_key: str | None = None,
|
||||
limit: int | None = None,
|
||||
next_cursor: SlackPaginationNextCursor | None = None,
|
||||
max_pagination_timeout_seconds: int = MAX_PAGINATION_TIMEOUT_SECONDS,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> tuple[list, Optional[SlackPaginationNextCursor]]:
|
||||
) -> tuple[list, SlackPaginationNextCursor | None]:
|
||||
"""Paginate a Slack AsyncWebClient's method results.
|
||||
|
||||
The purpose is to abstract the pagination work and make it easier for the LLM to retrieve the
|
||||
|
|
@ -425,7 +426,7 @@ def convert_datetime_to_unix_timestamp(datetime_str: str) -> int:
|
|||
|
||||
def convert_relative_datetime_to_unix_timestamp(
|
||||
relative_datetime: str,
|
||||
current_unix_timestamp: Optional[int] = None,
|
||||
current_unix_timestamp: int | None = None,
|
||||
) -> int:
|
||||
"""Convert a relative datetime string in the format 'DD:HH:MM' to unix timestamp.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_slack"
|
||||
version = "0.4.2"
|
||||
description = "Slack tools for LLMs"
|
||||
description = "Arcade.dev LLM tools for Slack"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -23,7 +23,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
from dataclasses import asdict, dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlaybackState:
|
||||
is_playing: Optional[bool] = None
|
||||
progress_ms: Optional[int] = (
|
||||
is_playing: bool | None = None
|
||||
progress_ms: int | None = (
|
||||
None # Progress into the currently playing track or episode in milliseconds
|
||||
)
|
||||
device_name: Optional[str] = None
|
||||
device_id: Optional[str] = None
|
||||
currently_playing_type: Optional[str] = None
|
||||
album_id: Optional[str] = None
|
||||
album_name: Optional[str] = None
|
||||
device_name: str | None = None
|
||||
device_id: str | None = None
|
||||
currently_playing_type: str | None = None
|
||||
album_id: str | None = None
|
||||
album_name: str | None = None
|
||||
album_artists: list[str] = field(default_factory=list)
|
||||
album_spotify_url: Optional[str] = None
|
||||
track_id: Optional[str] = None
|
||||
track_name: Optional[str] = None
|
||||
track_spotify_url: Optional[str] = None
|
||||
album_spotify_url: str | None = None
|
||||
track_id: str | None = None
|
||||
track_name: str | None = None
|
||||
track_spotify_url: str | None = None
|
||||
track_artists: list[str] = field(default_factory=list)
|
||||
track_artists_ids: list[str] = field(default_factory=list)
|
||||
show_name: Optional[str] = None
|
||||
show_id: Optional[str] = None
|
||||
show_spotify_url: Optional[str] = None
|
||||
episode_name: Optional[str] = None
|
||||
episode_id: Optional[str] = None
|
||||
episode_spotify_url: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
show_name: str | None = None
|
||||
show_id: str | None = None
|
||||
show_spotify_url: str | None = None
|
||||
episode_name: str | None = None
|
||||
episode_id: str | None = None
|
||||
episode_spotify_url: str | None = None
|
||||
message: str | None = None
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert the PlaybackState instance to a dictionary, excluding None values."""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -20,10 +20,10 @@ from arcade_spotify.tools.utils import (
|
|||
async def adjust_playback_position(
|
||||
context: ToolContext,
|
||||
absolute_position_ms: Annotated[
|
||||
Optional[int], "The absolute position in milliseconds to seek to"
|
||||
int | None, "The absolute position in milliseconds to seek to"
|
||||
] = None,
|
||||
relative_position_ms: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The relative position from the current playback position in milliseconds to seek to",
|
||||
] = None,
|
||||
) -> Annotated[str, "Success/failure message"]:
|
||||
|
|
@ -173,7 +173,7 @@ async def start_tracks_playback_by_id(
|
|||
"A list of Spotify track (song) IDs to play. Order of execution is not guarenteed.",
|
||||
],
|
||||
position_ms: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"The position in milliseconds to start the first track from",
|
||||
] = 0,
|
||||
) -> Annotated[str, "Success/failure message"]:
|
||||
|
|
@ -270,7 +270,7 @@ async def play_artist_by_name(
|
|||
async def play_track_by_name(
|
||||
context: ToolContext,
|
||||
track_name: Annotated[str, "The name of the track to play"],
|
||||
artist_name: Annotated[Optional[str], "The name of the artist of the track"] = None,
|
||||
artist_name: Annotated[str | None, "The name of the artist of the track"] = None,
|
||||
) -> Annotated[str, "Success/failure message"]:
|
||||
"""Plays a song by name"""
|
||||
q = f"track:{track_name}"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_spotify"
|
||||
version = "0.2.1"
|
||||
description = "Arcade tools for Spotify"
|
||||
description = "Arcade.dev LLM tools for Spotify"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from firecrawl import FirecrawlApp
|
||||
|
|
@ -14,20 +14,20 @@ async def scrape_url(
|
|||
context: ToolContext,
|
||||
url: Annotated[str, "URL to scrape"],
|
||||
formats: Annotated[
|
||||
Optional[list[Formats]], "Formats to retrieve. Defaults to ['markdown']."
|
||||
list[Formats] | None, "Formats to retrieve. Defaults to ['markdown']."
|
||||
] = None,
|
||||
only_main_content: Annotated[
|
||||
Optional[bool],
|
||||
bool | None,
|
||||
"Only return the main content of the page excluding headers, navs, footers, etc.",
|
||||
] = True,
|
||||
include_tags: Annotated[list[str] | None, "List of tags to include in the output"] = None,
|
||||
exclude_tags: Annotated[list[str] | None, "List of tags to exclude from the output"] = None,
|
||||
wait_for: Annotated[
|
||||
Optional[int],
|
||||
int | None,
|
||||
"Specify a delay in milliseconds before fetching the content, allowing the page "
|
||||
"sufficient time to load.",
|
||||
] = 10,
|
||||
timeout: Annotated[Optional[int], "Timeout in milliseconds for the request"] = 30000,
|
||||
timeout: Annotated[int | None, "Timeout in milliseconds for the request"] = 30000,
|
||||
) -> Annotated[dict[str, Any], "Scraped data in specified formats"]:
|
||||
"""Scrape a URL using Firecrawl and return the data in specified formats."""
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ async def crawl_website(
|
|||
] = False,
|
||||
allow_external_links: Annotated[bool, "Allow following links to external websites"] = False,
|
||||
webhook: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The URL to send a POST request to when the crawl is started, updated and completed.",
|
||||
] = None,
|
||||
async_crawl: Annotated[bool, "Run the crawl asynchronously"] = True,
|
||||
|
|
@ -163,7 +163,7 @@ async def cancel_crawl(
|
|||
async def map_website(
|
||||
context: ToolContext,
|
||||
url: Annotated[str, "The base URL to start crawling from"],
|
||||
search: Annotated[Optional[str], "Search query to use for mapping"] = None,
|
||||
search: Annotated[str | None, "Search query to use for mapping"] = None,
|
||||
ignore_sitemap: Annotated[bool, "Ignore the website sitemap when crawling"] = True,
|
||||
include_subdomains: Annotated[bool, "Include subdomains of the website"] = False,
|
||||
limit: Annotated[int, "Maximum number of links to return"] = 5000,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_web"
|
||||
version = "1.0.1"
|
||||
description = "LLM tools for web-related tasks"
|
||||
description = "Arcade.dev LLM tools for web scraping related tasks"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Any, Optional
|
||||
from typing import Annotated, Any
|
||||
|
||||
import httpx
|
||||
from arcade.sdk import ToolContext, tool
|
||||
|
|
@ -67,7 +67,7 @@ async def search_recent_tweets_by_username(
|
|||
int, "The maximum number of results to return. Must be in range [1, 100] inclusive"
|
||||
] = 10,
|
||||
next_token: Annotated[
|
||||
Optional[str], "The pagination token starting from which to return results"
|
||||
str | None, "The pagination token starting from which to return results"
|
||||
] = None,
|
||||
) -> Annotated[dict[str, Any], "Dictionary containing the search results"]:
|
||||
"""Search for recent tweets (last 7 days) on X (Twitter) by username.
|
||||
|
|
@ -121,7 +121,7 @@ async def search_recent_tweets_by_keywords(
|
|||
int, "The maximum number of results to return. Must be in range [1, 100] inclusive"
|
||||
] = 10,
|
||||
next_token: Annotated[
|
||||
Optional[str], "The pagination token starting from which to return results"
|
||||
str | None, "The pagination token starting from which to return results"
|
||||
] = None,
|
||||
) -> Annotated[dict[str, Any], "Dictionary containing the search results"]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ def parse_search_recent_tweets_response(response_data: dict[str, Any]) -> dict[s
|
|||
tweet["tweet_url"] = get_tweet_url(tweet["id"])
|
||||
|
||||
# Add 'author_username' and 'author_name' to each tweet
|
||||
for tweet_data, user_data in zip(response_data["data"], response_data["includes"]["users"]):
|
||||
for tweet_data, user_data in zip(
|
||||
response_data["data"], response_data["includes"]["users"], strict=False
|
||||
):
|
||||
tweet_data["author_username"] = user_data["username"]
|
||||
tweet_data["author_name"] = user_data["name"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_x"
|
||||
version = "0.1.12"
|
||||
description = "LLM tools for interacting with X (Twitter)"
|
||||
description = "Arcade.dev LLM tools for X (Twitter)"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
fix = true
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@ help:
|
|||
.PHONY: install
|
||||
install: ## Install the poetry environment and install the pre-commit hooks
|
||||
@echo "📦 Checking if Poetry is installed"
|
||||
@if ! command -v poetry &> /dev/null; then \
|
||||
@if ! command -v poetry >/dev/null 2>&1; then \
|
||||
echo "📦 Poetry not found. Checking if pip is available"; \
|
||||
if ! command -v pip >/dev/null 2>&1; then \
|
||||
echo "❌ pip is not installed. Please install pip first."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "📦 Installing Poetry with pip"; \
|
||||
pip install poetry==1.8.5; \
|
||||
else \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated
|
||||
|
||||
from arcade.sdk import ToolContext, tool
|
||||
from arcade.sdk.auth import Zoom
|
||||
|
|
@ -14,7 +14,7 @@ from arcade_zoom.tools.utils import _handle_zoom_api_error, _send_zoom_request
|
|||
async def list_upcoming_meetings(
|
||||
context: ToolContext,
|
||||
user_id: Annotated[
|
||||
Optional[str],
|
||||
str | None,
|
||||
"The user's user ID or email address. Defaults to 'me' for the current user.",
|
||||
] = "me",
|
||||
) -> Annotated[dict, "List of upcoming meetings within the next 24 hours"]:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_zoom"
|
||||
version = "0.1.10"
|
||||
description = "Arcade tools for Zoom"
|
||||
description = "Arcade.dev LLM tools for Zoom"
|
||||
authors = ["Arcade <dev@arcade.dev>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
|
|
@ -20,7 +20,7 @@ tox = "^4.11.1"
|
|||
ruff = "^0.7.4"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
requires = ["poetry-core>=1.0.0,<2.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.mypy]
|
||||
|
|
|
|||
Loading…
Reference in a new issue