Files
mailu-openapi-agent/agent.py
a2a-platform f733d482d7 deploy
2026-06-04 22:39:51 +00:00

1613 lines
86 KiB
Python

from __future__ import annotations
import asyncio
import base64
from contextlib import asynccontextmanager
from email.header import decode_header, make_header
from email.message import EmailMessage
from email.parser import BytesParser
from email.policy import default as default_email_policy
from email.utils import formatdate, make_msgid, parsedate_to_datetime
import imaplib
import json
import re
import smtplib
import ssl
from typing import Any
import httpx
from deepagents import create_deep_agent
from langchain_core.messages import BaseMessage
from langchain_core.tools import StructuredTool
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from a2a_pack import (
A2AAgent,
ConsumerSetup,
ConsumerSetupField,
ConsumerSetupMissing,
EgressPolicy,
LLMProvisioning,
Pricing,
Resources,
RunContext,
skill,
)
DEFAULT_BASE_URL = "https://mail.a2acloud.io/api/v1"
OPERATIONS = json.loads("{\n \"create_alias\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new alias\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_alias\",\n \"parameters\": [],\n \"path\": \"/alias\",\n \"request_body\": {\n \"$ref\": \"#/definitions/Alias\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_alias\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new alias\",\n \"tags\": [\n \"alias\"\n ]\n },\n \"create_alternative\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new alternative (for domain)\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_alternative\",\n \"parameters\": [],\n \"path\": \"/alternative\",\n \"request_body\": {\n \"$ref\": \"#/definitions/AlternativeDomain\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_alternative\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new alternative (for domain)\",\n \"tags\": [\n \"alternative\"\n ]\n },\n \"create_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new domain\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_domain\",\n \"parameters\": [],\n \"path\": \"/domain\",\n \"request_body\": {\n \"$ref\": \"#/definitions/Domain\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"create_manager\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new domain manager for the specified domain\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_manager\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}/manager\",\n \"request_body\": {\n \"$ref\": \"#/definitions/ManagerCreate\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_manager\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new domain manager for the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"create_relay\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new relay\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_relay\",\n \"parameters\": [],\n \"path\": \"/relay\",\n \"request_body\": {\n \"$ref\": \"#/definitions/Relay\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_relay\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new relay\",\n \"tags\": [\n \"relay\"\n ]\n },\n \"create_token\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new token\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_token\",\n \"parameters\": [],\n \"path\": \"/token\",\n \"request_body\": {\n \"$ref\": \"#/definitions/TokenPost\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_token\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new token\",\n \"tags\": [\n \"token\"\n ]\n },\n \"create_token_2\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new token for the specified user\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_token_2\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/tokenuser/{email}\",\n \"request_body\": {\n \"$ref\": \"#/definitions/TokenPost2\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_token_2\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new token for the specified user\",\n \"tags\": [\n \"token\"\n ]\n },\n \"create_user\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Create a new user\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"create_user\",\n \"parameters\": [],\n \"path\": \"/user\",\n \"request_body\": {\n \"$ref\": \"#/definitions/UserCreate\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"create_user\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Create a new user\",\n \"tags\": [\n \"user\"\n ]\n },\n \"delete_alias\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified alias\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_alias\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"alias\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/alias/{alias}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_alias\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified alias\",\n \"tags\": [\n \"alias\"\n ]\n },\n \"delete_alternative\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified alternative (for domain)\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_alternative\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"alt\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/alternative/{alt}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_alternative\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified alternative (for domain)\",\n \"tags\": [\n \"alternative\"\n ]\n },\n \"delete_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified domain\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_domain\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"delete_manager\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified manager of the specified domain\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_manager\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n },\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}/manager/{email}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_manager\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified manager of the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"delete_relay\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified relay\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_relay\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"name\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/relay/{name}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_relay\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified relay\",\n \"tags\": [\n \"relay\"\n ]\n },\n \"delete_token\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified token\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_token\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"token_id\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/token/{token_id}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_token\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified token\",\n \"tags\": [\n \"token\"\n ]\n },\n \"delete_user\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Delete the specified user\",\n \"destructive\": true,\n \"method\": \"DELETE\",\n \"operation_id\": \"delete_user\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/user/{email}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"delete_user\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Delete the specified user\",\n \"tags\": [\n \"user\"\n ]\n },\n \"find_alias\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up the specified alias\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_alias\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"alias\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/alias/{alias}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_alias\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up the specified alias\",\n \"tags\": [\n \"alias\"\n ]\n },\n \"find_alias_filter_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up the aliases of the specified domain\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_alias_filter_domain\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/alias/destination/{domain}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_alias_filter_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up the aliases of the specified domain\",\n \"tags\": [\n \"alias\"\n ]\n },\n \"find_alternative\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up the specified alternative (of domain)\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_alternative\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"alt\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/alternative/{alt}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_alternative\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up the specified alternative (of domain)\",\n \"tags\": [\n \"alternative\"\n ]\n },\n \"find_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up the specified domain\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_domain\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"find_manager\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Check if the specified user is a manager of the specified domain\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_manager\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n },\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}/manager/{email}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_manager\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Check if the specified user is a manager of the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"find_relay\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up the specified relay\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_relay\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"name\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/relay/{name}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_relay\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up the specified relay\",\n \"tags\": [\n \"relay\"\n ]\n },\n \"find_token\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Find the specified token\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_token\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"token_id\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/token/{token_id}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_token\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Find the specified token\",\n \"tags\": [\n \"token\"\n ]\n },\n \"find_tokens_of_user\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up all the tokens of the specified user\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_tokens_of_user\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/tokenuser/{email}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_tokens_of_user\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up all the tokens of the specified user\",\n \"tags\": [\n \"token\"\n ]\n },\n \"find_user\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Look up the specified user\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"find_user\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/user/{email}\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"find_user\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Look up the specified user\",\n \"tags\": [\n \"user\"\n ]\n },\n \"generate_dkim\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Generate new DKIM/DMARC keys for the specified domain\",\n \"destructive\": true,\n \"method\": \"POST\",\n \"operation_id\": \"generate_dkim\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}/dkim\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"generate_dkim\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Generate new DKIM/DMARC keys for the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"list_alias\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all aliases\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_alias\",\n \"parameters\": [],\n \"path\": \"/alias\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_alias\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all aliases\",\n \"tags\": [\n \"alias\"\n ]\n },\n \"list_alternative\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all alternatives\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_alternative\",\n \"parameters\": [],\n \"path\": \"/alternative\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_alternative\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all alternatives\",\n \"tags\": [\n \"alternative\"\n ]\n },\n \"list_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all domains\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_domain\",\n \"parameters\": [],\n \"path\": \"/domain\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all domains\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"list_managers\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all managers of the specified domain\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_managers\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}/manager\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_managers\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all managers of the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"list_relays\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all relays\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_relays\",\n \"parameters\": [],\n \"path\": \"/relay\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_relays\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all relays\",\n \"tags\": [\n \"relay\"\n ]\n },\n \"list_tokens\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all tokens\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_tokens\",\n \"parameters\": [],\n \"path\": \"/token\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_tokens\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all tokens\",\n \"tags\": [\n \"token\"\n ]\n },\n \"list_user\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all users\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_user\",\n \"parameters\": [],\n \"path\": \"/user\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_user\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all users\",\n \"tags\": [\n \"user\"\n ]\n },\n \"list_user_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"List all the users from the specified domain\",\n \"destructive\": false,\n \"method\": \"GET\",\n \"operation_id\": \"list_user_domain\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}/users\",\n \"request_body\": null,\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"list_user_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"List all the users from the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"update_alias\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Update the specfied alias\",\n \"destructive\": true,\n \"method\": \"PATCH\",\n \"operation_id\": \"update_alias\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"alias\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/alias/{alias}\",\n \"request_body\": {\n \"$ref\": \"#/definitions/AliasUpdate\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"update_alias\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Update the specfied alias\",\n \"tags\": [\n \"alias\"\n ]\n },\n \"update_domain\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Update the specified domain\",\n \"destructive\": true,\n \"method\": \"PATCH\",\n \"operation_id\": \"update_domain\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"domain\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/domain/{domain}\",\n \"request_body\": {\n \"$ref\": \"#/definitions/DomainUpdate\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"update_domain\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Update the specified domain\",\n \"tags\": [\n \"domain\"\n ]\n },\n \"update_relay\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Update the specified relay\",\n \"destructive\": true,\n \"method\": \"PATCH\",\n \"operation_id\": \"update_relay\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"name\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/relay/{name}\",\n \"request_body\": {\n \"$ref\": \"#/definitions/RelayUpdate\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"update_relay\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Update the specified relay\",\n \"tags\": [\n \"relay\"\n ]\n },\n \"update_token\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Update the specified token\",\n \"destructive\": true,\n \"method\": \"PATCH\",\n \"operation_id\": \"update_token\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"token_id\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/token/{token_id}\",\n \"request_body\": {\n \"$ref\": \"#/definitions/TokenPost2\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"update_token\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Update the specified token\",\n \"tags\": [\n \"token\"\n ]\n },\n \"update_user\": {\n \"base_url\": \"https://mail.a2acloud.io/api/v1\",\n \"base_url_field\": \"OPENAPI_BASE_URL\",\n \"description\": \"Update the specified user\",\n \"destructive\": true,\n \"method\": \"PATCH\",\n \"operation_id\": \"update_user\",\n \"parameters\": [\n {\n \"description\": \"\",\n \"in\": \"path\",\n \"name\": \"email\",\n \"required\": true,\n \"schema\": {\n \"type\": \"string\"\n }\n }\n ],\n \"path\": \"/user/{email}\",\n \"request_body\": {\n \"$ref\": \"#/definitions/UserUpdate\"\n },\n \"security\": [\n {\n \"Bearer\": []\n }\n ],\n \"skill_name\": \"update_user\",\n \"source_index\": 1,\n \"source_openapi_url\": \"https://mail.a2acloud.io/api/v1/swagger.json\",\n \"source_title\": \"Mailu API\",\n \"summary\": \"Update the specified user\",\n \"tags\": [\n \"user\"\n ]\n }\n}")
ROOT_SECURITY = json.loads("[\n {\n \"Bearer\": []\n }\n]")
SECURITY_SCHEMES = json.loads("{\n \"Bearer\": {\n \"in\": \"header\",\n \"name\": \"Authorization\",\n \"type\": \"apiKey\"\n }\n}")
SECURITY_FIELDS = json.loads("{\n \"Bearer\": {\n \"field\": \"AUTHORIZATION\",\n \"kind\": \"apiKey\",\n \"location\": \"header\",\n \"name\": \"Authorization\"\n }\n}")
PATH_PARAMETER_RE = re.compile(r"{([^}/]+)}")
class OperationInput(BaseModel):
parameters: dict[str, Any] = Field(
default_factory=dict,
description="Path, query, header, and cookie parameters keyed by OpenAPI parameter name.",
)
body: Any | None = Field(default=None, description="JSON request body, when the operation accepts one.")
class SendEmailInput(BaseModel):
email: str = Field(description="Mailbox email address to send as. A temporary Mailu token is minted internally.")
to: list[str] = Field(description="Recipient email addresses.")
subject: str = Field(description="Message subject.")
text_body: str = Field(description="Plain text message body.")
cc: list[str] = Field(default_factory=list, description="CC recipients.")
bcc: list[str] = Field(default_factory=list, description="BCC recipients.")
reply_to: str | None = Field(default=None, description="Optional Reply-To address.")
smtp_host: str = Field(default="mail.a2acloud.io", description="SMTP submission host.")
smtp_port: int = Field(default=587, description="SMTP port. Use 587 for STARTTLS or 465 for implicit TLS.")
class ReadInboxInput(BaseModel):
email: str = Field(description="Mailbox email address to read. A temporary Mailu token is minted internally.")
mailbox: str = Field(default="INBOX", description="IMAP mailbox name.")
limit: int = Field(default=10, ge=1, le=50, description="Maximum number of newest messages to return.")
unread_only: bool = Field(default=True, description="Only return unread messages.")
mark_seen: bool = Field(default=False, description="Mark returned messages as read.")
max_body_chars: int = Field(default=4000, ge=0, le=20000, description="Maximum plain text body characters per message.")
imap_host: str = Field(default="mail.a2acloud.io", description="IMAPS host.")
imap_port: int = Field(default=993, description="IMAPS port.")
class ReadSentInput(BaseModel):
email: str = Field(description="Mailbox email address to read. A temporary Mailu token is minted internally.")
mailbox: str = Field(default="Sent", description="IMAP sent-mail mailbox name. Override if the client uses another folder.")
limit: int = Field(default=10, ge=1, le=50, description="Maximum number of newest sent messages to return.")
max_body_chars: int = Field(default=4000, ge=0, le=20000, description="Maximum plain text body characters per message.")
imap_host: str = Field(default="mail.a2acloud.io", description="IMAPS host.")
imap_port: int = Field(default=993, description="IMAPS port.")
class MarkEmailReadInput(BaseModel):
email: str = Field(description="Mailbox email address. A temporary Mailu token is minted internally.")
uid: str = Field(description="IMAP UID returned by read_inbox.")
mailbox: str = Field(default="INBOX", description="IMAP mailbox name.")
imap_host: str = Field(default="mail.a2acloud.io", description="IMAPS host.")
imap_port: int = Field(default=993, description="IMAPS port.")
class MailuOpenapiAgent(A2AAgent):
name = "mailu-openapi-agent"
description = "Mailu administration agent generated from the live Mailu OpenAPI spec."
version = "1.2"
consumer_setup = ConsumerSetup.from_fields(
ConsumerSetupField.config("OPENAPI_BASE_URL", label="API base URL", description="Override the default API server (https://mail.a2acloud.io/api/v1).", required=False, input_type="url"),
ConsumerSetupField.secret("AUTHORIZATION", label="Bearer API key", description="Sent as header parameter 'Authorization'. Also used to mint temporary per-user Mailu mailbox tokens for SMTP/IMAP skills.", required=True),
)
llm_provisioning = LLMProvisioning.PLATFORM
pricing = Pricing(
price_per_call_usd=0.0,
caller_pays_llm=False,
notes="Uses a scoped platform LLM grant through ctx.llm.",
)
resources = Resources(cpu="200m", memory="512Mi")
egress = EgressPolicy(
allow_hosts=('mail.a2acloud.io',),
deny_internet_by_default=True,
)
tools_used = ("openapi", "deepagents")
capabilities = {
"openapi_auto_agent": {
"operation_count": len(OPERATIONS),
"default_base_url": DEFAULT_BASE_URL,
"source_openapi_url": 'https://mail.a2acloud.io/api/v1/swagger.json',
"source_openapi_urls": ['https://mail.a2acloud.io/api/v1/swagger.json'],
"server_urls": ['https://mail.a2acloud.io/api/v1'],
"regenerable": True,
"security_schemes": list(SECURITY_SCHEMES),
}
}
@skill(
name="auto",
description="Use the OpenAPI service to complete a natural-language goal.",
tags=("openapi", "auto"),
timeout_seconds=180,
)
async def auto(self, ctx: RunContext, goal: str) -> dict[str, Any]:
creds = ctx.llm
if not creds.api_key:
return {
"error": "llm_credentials_missing",
"final": (
"LLM credentials were not available for this run. Hosted "
"generated OpenAPI agents should receive a platform LLM grant."
),
"messages": [],
}
model_kwargs: dict[str, Any] = {
"model": creds.model,
"base_url": creds.base_url,
"api_key": creds.api_key,
}
if creds.temperature_mode != "omit" and creds.temperature is not None:
model_kwargs["temperature"] = creds.temperature
if creds.extra_body:
model_kwargs["extra_body"] = dict(creds.extra_body)
model = ChatOpenAI(**model_kwargs)
graph = create_deep_agent(
model=model,
tools=self._operation_tools(ctx),
system_prompt=self._system_prompt(),
)
result = await graph.ainvoke({"messages": [{"role": "user", "content": goal}]})
messages = result.get("messages", []) if isinstance(result, dict) else []
final = _message_text(messages[-1]) if messages else result
return {
"final": final,
"messages": [_message_to_dict(message) for message in messages[-8:]],
}
@skill(
name="send_email",
description="Mint a temporary Mailu token for a mailbox, send an email over SMTP submission, then delete the token.",
tags=("mailbox", "smtp", "email"),
timeout_seconds=120,
)
async def send_email(
self,
ctx: RunContext,
email: str,
to: list[str],
subject: str,
text_body: str,
cc: list[str] | None = None,
bcc: list[str] | None = None,
reply_to: str | None = None,
smtp_host: str = "mail.a2acloud.io",
smtp_port: int = 587,
) -> dict[str, Any]:
if not to:
return {"ok": False, "error": "recipient_required"}
async with self._temporary_mail_token(ctx, email, "send_email") as token_info:
return await asyncio.to_thread(
_send_email_sync,
email,
token_info["token"],
to,
subject,
text_body,
cc or [],
bcc or [],
reply_to,
smtp_host,
smtp_port,
)
@skill(
name="read_inbox",
description="Mint a temporary Mailu token for a mailbox, read messages over IMAPS, then delete the token.",
tags=("mailbox", "imap", "email"),
timeout_seconds=120,
)
async def read_inbox(
self,
ctx: RunContext,
email: str,
mailbox: str = "INBOX",
limit: int = 10,
unread_only: bool = True,
mark_seen: bool = False,
max_body_chars: int = 4000,
imap_host: str = "mail.a2acloud.io",
imap_port: int = 993,
) -> dict[str, Any]:
async with self._temporary_mail_token(ctx, email, "read_inbox") as token_info:
return await asyncio.to_thread(
_read_inbox_sync,
email,
token_info["token"],
mailbox,
limit,
unread_only,
mark_seen,
max_body_chars,
imap_host,
imap_port,
)
@skill(
name="read_sent",
description="Mint a temporary Mailu token for a mailbox, read sent messages over IMAPS, then delete the token.",
tags=("mailbox", "imap", "email"),
timeout_seconds=120,
)
async def read_sent(
self,
ctx: RunContext,
email: str,
mailbox: str = "Sent",
limit: int = 10,
max_body_chars: int = 4000,
imap_host: str = "mail.a2acloud.io",
imap_port: int = 993,
) -> dict[str, Any]:
async with self._temporary_mail_token(ctx, email, "read_sent") as token_info:
return await asyncio.to_thread(
_read_mailbox_sync,
email,
token_info["token"],
mailbox,
limit,
"ALL",
False,
max_body_chars,
imap_host,
imap_port,
)
@skill(
name="mark_email_read",
description="Mint a temporary Mailu token for a mailbox, mark one IMAP UID as read, then delete the token.",
tags=("mailbox", "imap", "email"),
timeout_seconds=60,
)
async def mark_email_read(
self,
ctx: RunContext,
email: str,
uid: str,
mailbox: str = "INBOX",
imap_host: str = "mail.a2acloud.io",
imap_port: int = 993,
) -> dict[str, Any]:
async with self._temporary_mail_token(ctx, email, "mark_email_read") as token_info:
return await asyncio.to_thread(
_mark_email_read_sync,
email,
token_info["token"],
uid,
mailbox,
imap_host,
imap_port,
)
@skill(
name="create_alias",
description="POST /alias - Create a new alias",
tags=('alias',),
timeout_seconds=120,
)
async def create_alias(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_alias",
parameters=parameters or {},
body=body,
)
@skill(
name="list_alias",
description="GET /alias - List all aliases",
tags=('alias',),
timeout_seconds=60,
)
async def list_alias(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_alias",
parameters=parameters or {},
body=body,
)
@skill(
name="find_alias_filter_domain",
description="GET /alias/destination/{domain} - Look up the aliases of the specified domain",
tags=('alias',),
timeout_seconds=60,
)
async def find_alias_filter_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_alias_filter_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="update_alias",
description="PATCH /alias/{alias} - Update the specfied alias",
tags=('alias',),
timeout_seconds=120,
)
async def update_alias(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"update_alias",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_alias",
description="DELETE /alias/{alias} - Delete the specified alias",
tags=('alias',),
timeout_seconds=120,
)
async def delete_alias(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_alias",
parameters=parameters or {},
body=body,
)
@skill(
name="find_alias",
description="GET /alias/{alias} - Look up the specified alias",
tags=('alias',),
timeout_seconds=60,
)
async def find_alias(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_alias",
parameters=parameters or {},
body=body,
)
@skill(
name="create_alternative",
description="POST /alternative - Create a new alternative (for domain)",
tags=('alternative',),
timeout_seconds=120,
)
async def create_alternative(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_alternative",
parameters=parameters or {},
body=body,
)
@skill(
name="list_alternative",
description="GET /alternative - List all alternatives",
tags=('alternative',),
timeout_seconds=60,
)
async def list_alternative(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_alternative",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_alternative",
description="DELETE /alternative/{alt} - Delete the specified alternative (for domain)",
tags=('alternative',),
timeout_seconds=120,
)
async def delete_alternative(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_alternative",
parameters=parameters or {},
body=body,
)
@skill(
name="find_alternative",
description="GET /alternative/{alt} - Look up the specified alternative (of domain)",
tags=('alternative',),
timeout_seconds=60,
)
async def find_alternative(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_alternative",
parameters=parameters or {},
body=body,
)
@skill(
name="create_domain",
description="POST /domain - Create a new domain",
tags=('domain',),
timeout_seconds=120,
)
async def create_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="list_domain",
description="GET /domain - List all domains",
tags=('domain',),
timeout_seconds=60,
)
async def list_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="update_domain",
description="PATCH /domain/{domain} - Update the specified domain",
tags=('domain',),
timeout_seconds=120,
)
async def update_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"update_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_domain",
description="DELETE /domain/{domain} - Delete the specified domain",
tags=('domain',),
timeout_seconds=120,
)
async def delete_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="find_domain",
description="GET /domain/{domain} - Look up the specified domain",
tags=('domain',),
timeout_seconds=60,
)
async def find_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="generate_dkim",
description="POST /domain/{domain}/dkim - Generate new DKIM/DMARC keys for the specified domain",
tags=('domain',),
timeout_seconds=120,
)
async def generate_dkim(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"generate_dkim",
parameters=parameters or {},
body=body,
)
@skill(
name="create_manager",
description="POST /domain/{domain}/manager - Create a new domain manager for the specified domain",
tags=('domain',),
timeout_seconds=120,
)
async def create_manager(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_manager",
parameters=parameters or {},
body=body,
)
@skill(
name="list_managers",
description="GET /domain/{domain}/manager - List all managers of the specified domain",
tags=('domain',),
timeout_seconds=60,
)
async def list_managers(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_managers",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_manager",
description="DELETE /domain/{domain}/manager/{email} - Delete the specified manager of the specified domain",
tags=('domain',),
timeout_seconds=120,
)
async def delete_manager(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_manager",
parameters=parameters or {},
body=body,
)
@skill(
name="find_manager",
description="GET /domain/{domain}/manager/{email} - Check if the specified user is a manager of the specified domain",
tags=('domain',),
timeout_seconds=60,
)
async def find_manager(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_manager",
parameters=parameters or {},
body=body,
)
@skill(
name="list_user_domain",
description="GET /domain/{domain}/users - List all the users from the specified domain",
tags=('domain',),
timeout_seconds=60,
)
async def list_user_domain(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_user_domain",
parameters=parameters or {},
body=body,
)
@skill(
name="create_relay",
description="POST /relay - Create a new relay",
tags=('relay',),
timeout_seconds=120,
)
async def create_relay(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_relay",
parameters=parameters or {},
body=body,
)
@skill(
name="list_relays",
description="GET /relay - List all relays",
tags=('relay',),
timeout_seconds=60,
)
async def list_relays(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_relays",
parameters=parameters or {},
body=body,
)
@skill(
name="update_relay",
description="PATCH /relay/{name} - Update the specified relay",
tags=('relay',),
timeout_seconds=120,
)
async def update_relay(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"update_relay",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_relay",
description="DELETE /relay/{name} - Delete the specified relay",
tags=('relay',),
timeout_seconds=120,
)
async def delete_relay(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_relay",
parameters=parameters or {},
body=body,
)
@skill(
name="find_relay",
description="GET /relay/{name} - Look up the specified relay",
tags=('relay',),
timeout_seconds=60,
)
async def find_relay(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_relay",
parameters=parameters or {},
body=body,
)
@skill(
name="create_token",
description="POST /token - Create a new token",
tags=('token',),
timeout_seconds=120,
)
async def create_token(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_token",
parameters=parameters or {},
body=body,
)
@skill(
name="list_tokens",
description="GET /token - List all tokens",
tags=('token',),
timeout_seconds=60,
)
async def list_tokens(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_tokens",
parameters=parameters or {},
body=body,
)
@skill(
name="update_token",
description="PATCH /token/{token_id} - Update the specified token",
tags=('token',),
timeout_seconds=120,
)
async def update_token(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"update_token",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_token",
description="DELETE /token/{token_id} - Delete the specified token",
tags=('token',),
timeout_seconds=120,
)
async def delete_token(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_token",
parameters=parameters or {},
body=body,
)
@skill(
name="find_token",
description="GET /token/{token_id} - Find the specified token",
tags=('token',),
timeout_seconds=60,
)
async def find_token(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_token",
parameters=parameters or {},
body=body,
)
@skill(
name="create_token_2",
description="POST /tokenuser/{email} - Create a new token for the specified user",
tags=('token',),
timeout_seconds=120,
)
async def create_token_2(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_token_2",
parameters=parameters or {},
body=body,
)
@skill(
name="find_tokens_of_user",
description="GET /tokenuser/{email} - Look up all the tokens of the specified user",
tags=('token',),
timeout_seconds=60,
)
async def find_tokens_of_user(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_tokens_of_user",
parameters=parameters or {},
body=body,
)
@skill(
name="create_user",
description="POST /user - Create a new user",
tags=('user',),
timeout_seconds=120,
)
async def create_user(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"create_user",
parameters=parameters or {},
body=body,
)
@skill(
name="list_user",
description="GET /user - List all users",
tags=('user',),
timeout_seconds=60,
)
async def list_user(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"list_user",
parameters=parameters or {},
body=body,
)
@skill(
name="update_user",
description="PATCH /user/{email} - Update the specified user",
tags=('user',),
timeout_seconds=120,
)
async def update_user(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"update_user",
parameters=parameters or {},
body=body,
)
@skill(
name="delete_user",
description="DELETE /user/{email} - Delete the specified user",
tags=('user',),
timeout_seconds=120,
)
async def delete_user(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"delete_user",
parameters=parameters or {},
body=body,
)
@skill(
name="find_user",
description="GET /user/{email} - Look up the specified user",
tags=('user',),
timeout_seconds=60,
)
async def find_user(
self,
ctx: RunContext,
parameters: dict[str, Any] | None = None,
body: Any | None = None,
) -> dict[str, Any]:
return await self._request(
ctx,
"find_user",
parameters=parameters or {},
body=body,
)
def _operation_tools(self, ctx: RunContext) -> list[StructuredTool]:
async def send_email_tool(
email: str,
to: list[str],
subject: str,
text_body: str,
cc: list[str] | None = None,
bcc: list[str] | None = None,
reply_to: str | None = None,
smtp_host: str = "mail.a2acloud.io",
smtp_port: int = 587,
) -> dict[str, Any]:
return await self.send_email(
ctx,
email=email,
to=to,
subject=subject,
text_body=text_body,
cc=cc,
bcc=bcc,
reply_to=reply_to,
smtp_host=smtp_host,
smtp_port=smtp_port,
)
async def read_inbox_tool(
email: str,
mailbox: str = "INBOX",
limit: int = 10,
unread_only: bool = True,
mark_seen: bool = False,
max_body_chars: int = 4000,
imap_host: str = "mail.a2acloud.io",
imap_port: int = 993,
) -> dict[str, Any]:
return await self.read_inbox(
ctx,
email=email,
mailbox=mailbox,
limit=limit,
unread_only=unread_only,
mark_seen=mark_seen,
max_body_chars=max_body_chars,
imap_host=imap_host,
imap_port=imap_port,
)
async def read_sent_tool(
email: str,
mailbox: str = "Sent",
limit: int = 10,
max_body_chars: int = 4000,
imap_host: str = "mail.a2acloud.io",
imap_port: int = 993,
) -> dict[str, Any]:
return await self.read_sent(
ctx,
email=email,
mailbox=mailbox,
limit=limit,
max_body_chars=max_body_chars,
imap_host=imap_host,
imap_port=imap_port,
)
async def mark_email_read_tool(
email: str,
uid: str,
mailbox: str = "INBOX",
imap_host: str = "mail.a2acloud.io",
imap_port: int = 993,
) -> dict[str, Any]:
return await self.mark_email_read(
ctx,
email=email,
uid=uid,
mailbox=mailbox,
imap_host=imap_host,
imap_port=imap_port,
)
tools: list[StructuredTool] = [
StructuredTool.from_function(
coroutine=send_email_tool,
name="send_email",
description="Send email through Mailu SMTP. Provide the mailbox email; the tool mints and deletes a temporary Mailu user token internally.",
args_schema=SendEmailInput,
),
StructuredTool.from_function(
coroutine=read_inbox_tool,
name="read_inbox",
description="Read Mailu mailbox messages through IMAPS. Provide the mailbox email; the tool mints and deletes a temporary Mailu user token internally.",
args_schema=ReadInboxInput,
),
StructuredTool.from_function(
coroutine=read_sent_tool,
name="read_sent",
description="Read Mailu sent-folder messages through IMAPS. Provide the mailbox email; the tool mints and deletes a temporary Mailu user token internally.",
args_schema=ReadSentInput,
),
StructuredTool.from_function(
coroutine=mark_email_read_tool,
name="mark_email_read",
description="Mark a Mailu mailbox message as read by IMAP UID. Provide the mailbox email; the tool mints and deletes a temporary Mailu user token internally.",
args_schema=MarkEmailReadInput,
),
]
for operation_id, operation in OPERATIONS.items():
async def call(
parameters: dict[str, Any] | None = None,
body: Any | None = None,
*,
_operation_id: str = operation_id,
) -> dict[str, Any]:
return await self._request(
ctx,
_operation_id,
parameters=parameters or {},
body=body,
)
tools.append(
StructuredTool.from_function(
coroutine=call,
name=operation["skill_name"],
description=self._tool_description(operation),
args_schema=OperationInput,
)
)
return tools
def _system_prompt(self) -> str:
lines = [
"You operate an API through generated OpenAPI tools.",
"Call tools to get real results. Do not invent API responses.",
"For write, update, or delete operations, explain the intended action before calling the tool.",
"For mailbox tasks, use send_email, read_inbox, read_sent, and mark_email_read. These tools mint temporary Mailu user tokens internally; never ask the user for or reveal mailbox tokens.",
"If an operation reports missing consumer setup, tell the user which setup field is required.",
"If an operation returns 404, 405, 410, or a schema/validation error that suggests the live API no longer matches these tools, tell the user this generated agent may need to be refreshed from the latest OpenAPI spec and ask whether they want to refresh it.",
"",
"Mailbox tools:",
"- send_email: WRITE SMTP submission using a temporary per-user token.",
"- read_inbox: READ IMAPS mailbox messages using a temporary per-user token.",
"- read_sent: READ sent-folder messages using a temporary per-user token.",
"- mark_email_read: WRITE IMAP seen flag using a temporary per-user token.",
"",
"Available operations:",
]
for operation in OPERATIONS.values():
marker = "WRITE" if operation.get("destructive") else "READ"
lines.append(
f"- {operation['skill_name']}: {marker} {operation['method']} {operation['path']}{operation['summary']}"
)
return "\n".join(lines)
def _tool_description(self, operation: dict[str, Any]) -> str:
parts = [
f"{operation['method']} {operation['path']}",
str(operation.get("description") or operation.get("summary") or ""),
]
if operation.get("parameters"):
parts.append(
"Parameters: "
+ ", ".join(
f"{p['name']} in {p['in']}{' required' if p.get('required') else ''}"
for p in operation["parameters"]
)
)
if operation.get("request_body") is not None:
parts.append("Accepts a JSON request body.")
return "\n".join(part for part in parts if part)
@asynccontextmanager
async def _temporary_mail_token(
self,
ctx: RunContext,
email_address: str,
purpose: str,
):
created = await self._request(
ctx,
"create_token_2",
parameters={"email": email_address},
body={"comment": f"mailu-openapi-agent temporary token for {purpose}"},
)
if not created.get("ok"):
raise RuntimeError(f"failed to mint temporary Mailu token: {created}")
token_info = _extract_mailu_token(created.get("result"))
try:
yield token_info
finally:
token_id = token_info.get("id")
if token_id:
await self._request(
ctx,
"delete_token",
parameters={"token_id": token_id},
body=None,
)
async def _request(
self,
ctx: RunContext,
operation_id: str,
*,
parameters: dict[str, Any],
body: Any | None,
) -> dict[str, Any]:
operation = OPERATIONS[operation_id]
default_base_url = str(operation.get("base_url") or DEFAULT_BASE_URL).rstrip("/")
base_url_field = str(operation.get("base_url_field") or "OPENAPI_BASE_URL")
base_url = str(ctx.consumer_config(base_url_field, default_base_url) or default_base_url).rstrip("/")
url, query, headers = self._request_parts(ctx, operation, parameters)
request_kwargs: dict[str, Any] = {
"params": query,
"headers": headers,
}
if body is not None:
if isinstance(body, (bytes, bytearray)):
body = body.decode("utf-8", errors="replace")
if isinstance(body, str):
stripped = body.strip()
if stripped and stripped[0] in "{[":
try:
body = json.loads(stripped)
except ValueError:
pass
request_kwargs["json"] = body
async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
response = await client.request(
operation["method"],
f"{base_url}{url}",
**request_kwargs,
)
content_type = response.headers.get("content-type", "")
try:
payload: Any = response.json() if "json" in content_type else response.text
except ValueError:
payload = response.text
if response.status_code >= 400:
out = {
"ok": False,
"status_code": response.status_code,
"operation_id": operation_id,
"error": payload,
}
if response.status_code in {404, 405, 410, 422}:
out["stale_openapi_hint"] = (
"This generated OpenAPI agent may be stale. Ask the user "
"whether they want to refresh it from the latest OpenAPI spec."
)
return out
return {
"ok": True,
"status_code": response.status_code,
"operation_id": operation_id,
"result": payload,
}
def _request_parts(
self,
ctx: RunContext,
operation: dict[str, Any],
parameters: dict[str, Any],
) -> tuple[str, dict[str, Any], dict[str, str]]:
path = operation["path"]
query: dict[str, Any] = {}
headers: dict[str, str] = {}
cookies: dict[str, Any] = {}
for param in operation.get("parameters") or []:
name = param["name"]
if name not in parameters:
if param.get("required"):
raise ValueError(f"missing required parameter {name!r}")
continue
value = parameters[name]
location = param["in"]
if location == "path":
path = path.replace("{" + name + "}", str(value))
elif location == "query":
query[name] = value
elif location == "header":
headers[name] = str(value)
elif location == "cookie":
cookies[name] = value
unresolved = PATH_PARAMETER_RE.findall(path)
if unresolved:
missing = ", ".join(sorted(set(unresolved)))
raise ValueError("missing required path parameter(s): " + missing)
if cookies:
headers["cookie"] = "; ".join(f"{key}={value}" for key, value in cookies.items())
auth_headers, auth_query = self._auth_for_operation(ctx, operation)
headers.update(auth_headers)
query.update(auth_query)
return path, query, headers
def _auth_for_operation(self, ctx: RunContext, operation: dict[str, Any]) -> tuple[dict[str, str], dict[str, Any]]:
requirements = operation.get("security")
if requirements is None:
requirements = ROOT_SECURITY
if requirements == []:
return {}, {}
missing: list[str] = []
for requirement in requirements or []:
if not isinstance(requirement, dict) or not requirement:
return {}, {}
headers: dict[str, str] = {}
query: dict[str, Any] = {}
ok = True
for scheme_name in requirement:
mapping = SECURITY_FIELDS.get(scheme_name) or {}
secret_name = mapping.get("field")
value = _consumer_secret_optional(ctx, secret_name) if secret_name else ""
if not value:
ok = False
if secret_name:
missing.append(secret_name)
continue
kind = mapping.get("kind")
if kind == "apiKey":
prefix = mapping.get("prefix") or ""
sent_value = f"{prefix}{value}"
if mapping.get("location") == "query":
query[mapping.get("name") or scheme_name] = sent_value
else:
headers[mapping.get("name") or scheme_name] = sent_value
elif kind in {"http", "oauth2"}:
scheme = mapping.get("scheme") or "Bearer"
if scheme.lower() == "basic":
token = base64.b64encode(value.encode("utf-8")).decode("ascii")
headers["authorization"] = f"Basic {token}"
else:
headers["authorization"] = f"{scheme} {value}"
else:
ok = False
if ok:
return headers, query
if missing:
raise ConsumerSetupMissing(
"operation requires consumer setup secret(s): "
+ ", ".join(sorted(set(missing)))
)
return {}, {}
def _consumer_secret_optional(ctx: RunContext, name: str | None) -> str:
if not name:
return ""
try:
return ctx.consumer_secret(name)
except ConsumerSetupMissing:
return ""
def _extract_mailu_token(payload: Any) -> dict[str, str]:
if not isinstance(payload, dict):
raise RuntimeError("Mailu token response was not a JSON object")
token = payload.get("token")
token_id = payload.get("id")
if not token:
nested = payload.get("result")
if isinstance(nested, dict):
token = nested.get("token")
token_id = token_id or nested.get("id")
if not token:
raise RuntimeError("Mailu token response did not include a token")
return {"token": str(token), "id": str(token_id) if token_id is not None else ""}
def _send_email_sync(
email_address: str,
token: str,
to: list[str],
subject: str,
text_body: str,
cc: list[str],
bcc: list[str],
reply_to: str | None,
smtp_host: str,
smtp_port: int,
) -> dict[str, Any]:
recipients = [addr for addr in [*to, *cc, *bcc] if addr]
if not recipients:
return {"ok": False, "error": "recipient_required"}
message = EmailMessage()
message["From"] = email_address
message["To"] = ", ".join(to)
if cc:
message["Cc"] = ", ".join(cc)
if reply_to:
message["Reply-To"] = reply_to
message["Subject"] = subject
message["Date"] = formatdate(localtime=True)
message["Message-ID"] = make_msgid(domain=email_address.split("@")[-1])
message.set_content(text_body)
context = ssl.create_default_context()
if smtp_port == 465:
with smtplib.SMTP_SSL(smtp_host, smtp_port, timeout=30, context=context) as server:
server.login(email_address, token)
refused = server.send_message(message, from_addr=email_address, to_addrs=recipients)
else:
with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as server:
server.ehlo()
server.starttls(context=context)
server.ehlo()
server.login(email_address, token)
refused = server.send_message(message, from_addr=email_address, to_addrs=recipients)
return {
"ok": not bool(refused),
"sent": not bool(refused),
"from": email_address,
"to": to,
"cc": cc,
"refused_recipients": refused,
}
def _read_inbox_sync(
email_address: str,
token: str,
mailbox: str,
limit: int,
unread_only: bool,
mark_seen: bool,
max_body_chars: int,
imap_host: str,
imap_port: int,
) -> dict[str, Any]:
return _read_mailbox_sync(
email_address,
token,
mailbox,
limit,
"UNSEEN" if unread_only else "ALL",
mark_seen,
max_body_chars,
imap_host,
imap_port,
)
def _read_mailbox_sync(
email_address: str,
token: str,
mailbox: str,
limit: int,
criteria: str,
mark_seen: bool,
max_body_chars: int,
imap_host: str,
imap_port: int,
) -> dict[str, Any]:
limit = max(1, min(limit, 50))
messages: list[dict[str, Any]] = []
with imaplib.IMAP4_SSL(imap_host, imap_port, timeout=30) as imap:
imap.login(email_address, token)
status, _ = imap.select(mailbox, readonly=not mark_seen)
if status != "OK":
return {"ok": False, "error": "mailbox_select_failed", "mailbox": mailbox}
status, data = imap.uid("search", None, criteria)
if status != "OK":
return {"ok": False, "error": "search_failed", "mailbox": mailbox}
uids = (data[0] or b"").split()
for uid_bytes in reversed(uids[-limit:]):
uid = uid_bytes.decode("ascii", errors="replace")
fetch_item = "(FLAGS BODY[])" if mark_seen else "(FLAGS BODY.PEEK[])"
status, fetched = imap.uid("fetch", uid, fetch_item)
if status != "OK":
messages.append({"uid": uid, "ok": False, "error": "fetch_failed"})
continue
raw_message = _first_fetch_bytes(fetched)
if raw_message is None:
messages.append({"uid": uid, "ok": False, "error": "message_missing"})
continue
parsed = BytesParser(policy=default_email_policy).parsebytes(raw_message)
flags = _fetch_flags(fetched)
messages.append(_message_summary(uid, parsed, flags, max_body_chars))
return {
"ok": True,
"mailbox": mailbox,
"email": email_address,
"count": len(messages),
"messages": messages,
}
def _mark_email_read_sync(
email_address: str,
token: str,
uid: str,
mailbox: str,
imap_host: str,
imap_port: int,
) -> dict[str, Any]:
with imaplib.IMAP4_SSL(imap_host, imap_port, timeout=30) as imap:
imap.login(email_address, token)
status, _ = imap.select(mailbox)
if status != "OK":
return {"ok": False, "error": "mailbox_select_failed", "mailbox": mailbox, "uid": uid}
status, data = imap.uid("store", uid, "+FLAGS", "(\\Seen)")
return {
"ok": status == "OK",
"mailbox": mailbox,
"uid": uid,
"result": [part.decode("utf-8", errors="replace") if isinstance(part, bytes) else part for part in data],
}
def _first_fetch_bytes(fetched: list[Any]) -> bytes | None:
for item in fetched:
if isinstance(item, tuple) and len(item) >= 2 and isinstance(item[1], bytes):
return item[1]
return None
def _fetch_flags(fetched: list[Any]) -> list[str]:
out: list[str] = []
for item in fetched:
chunks = item if isinstance(item, tuple) else (item,)
for chunk in chunks:
if not isinstance(chunk, bytes):
continue
match = re.search(rb"FLAGS \(([^)]*)\)", chunk)
if match:
out.extend(flag.decode("ascii", errors="replace") for flag in match.group(1).split())
return sorted(set(out))
def _message_summary(uid: str, message: EmailMessage, flags: list[str], max_body_chars: int) -> dict[str, Any]:
date_value = message.get("date")
parsed_date = None
if date_value:
try:
parsed_date = parsedate_to_datetime(date_value).isoformat()
except (TypeError, ValueError):
parsed_date = str(date_value)
body = _plain_text_body(message)
if max_body_chars >= 0:
body = body[:max_body_chars]
return {
"uid": uid,
"message_id": message.get("message-id"),
"subject": _decode_header_value(message.get("subject")),
"from": _decode_header_value(message.get("from")),
"to": _decode_header_value(message.get("to")),
"date": parsed_date,
"flags": flags,
"seen": "\\Seen" in flags,
"body": body,
}
def _plain_text_body(message: EmailMessage) -> str:
if message.is_multipart():
for part in message.walk():
if part.get_content_type() == "text/plain" and not part.get_filename():
return _part_text(part)
for part in message.walk():
if part.get_content_type() == "text/html" and not part.get_filename():
return _part_text(part)
return ""
return _part_text(message)
def _part_text(part: EmailMessage) -> str:
try:
content = part.get_content()
except (LookupError, UnicodeDecodeError):
payload = part.get_payload(decode=True) or b""
charset = part.get_content_charset() or "utf-8"
return payload.decode(charset, errors="replace")
return content if isinstance(content, str) else str(content)
def _decode_header_value(value: str | None) -> str | None:
if value is None:
return None
try:
return str(make_header(decode_header(value)))
except (LookupError, UnicodeDecodeError, ValueError):
return value
def _message_text(message: Any) -> Any:
content = getattr(message, "content", message)
return content
def _message_to_dict(message: Any) -> dict[str, Any]:
if isinstance(message, BaseMessage):
return message.model_dump(mode="json")
if hasattr(message, "model_dump"):
return message.model_dump(mode="json")
if isinstance(message, dict):
return message
return {"content": str(message)}
agent = MailuOpenapiAgent()