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