feat: add Omega Mac native macOS SwiftUI chat app
Native macOS counterpart to the web Omega agent, connecting directly to the OpenAI API and TPMJS tool registry (1M+ AI-ready tools). - SwiftUI app targeting macOS 15+ with dark theme - Full agentic loop: auto-discover tools via BM25, stream OpenAI responses, execute tools via remote sandbox, loop up to 10x - SwiftData persistence for conversations, messages, tool runs - Keychain storage for API keys and environment variables - SSE streaming via URLSession.bytes with custom parser - Actor-based services (OpenAIService, TPMJSRegistryService) - NavigationSplitView layout with sidebar + chat detail - MarkdownUI for rendering assistant responses - Settings: API key, model picker, env vars, custom system prompt - Keyboard shortcuts: Cmd+N new chat, Cmd+, settings, Enter send Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c8e8a7d9b4
commit
1cd44b4e97
45 changed files with 4583 additions and 0 deletions
77
packages/tools/official/agentmail/README.md
Normal file
77
packages/tools/official/agentmail/README.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# @tpmjs/tools-agentmail
|
||||
|
||||
AgentMail API tools for AI agents — create inboxes, send/receive emails, manage threads, drafts, and more.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @tpmjs/tools-agentmail
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
Set the `AGENTMAIL_API_KEY` environment variable. Get your API key from [AgentMail Dashboard](https://app.agentmail.to).
|
||||
|
||||
```bash
|
||||
export AGENTMAIL_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { createInbox, sendMessage, listMessages } from '@tpmjs/tools-agentmail';
|
||||
|
||||
// Create a new inbox for your AI agent
|
||||
const inbox = await createInbox.execute({
|
||||
username: 'my-agent',
|
||||
display_name: 'My AI Agent',
|
||||
});
|
||||
|
||||
// Send an email
|
||||
const message = await sendMessage.execute({
|
||||
inbox_id: inbox.inbox_id,
|
||||
to: 'recipient@example.com',
|
||||
subject: 'Hello from AI',
|
||||
text: 'This is an automated message from my AI agent.',
|
||||
});
|
||||
|
||||
// List received messages
|
||||
const messages = await listMessages.execute({
|
||||
inbox_id: inbox.inbox_id,
|
||||
limit: 20,
|
||||
});
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| createInbox | Create a new email inbox with optional custom username and domain |
|
||||
| listInboxes | List all email inboxes with pagination support |
|
||||
| getInbox | Get details of a specific inbox by ID |
|
||||
| deleteInbox | Delete an inbox and all its messages permanently |
|
||||
| sendMessage | Send an email message with subject and body |
|
||||
| replyToMessage | Reply to an existing email in a thread |
|
||||
| listMessages | List email messages in an inbox with pagination |
|
||||
| getMessage | Get full details of a specific message |
|
||||
| listThreads | List email threads, optionally filtered by labels |
|
||||
| getThread | Get a thread with all its messages |
|
||||
| createDraft | Create an email draft for approval before sending |
|
||||
| sendDraft | Send a previously created draft |
|
||||
|
||||
## Features
|
||||
|
||||
- **Complete Email Management**: Create inboxes, send/receive emails, manage threads
|
||||
- **Thread Support**: View full conversation history with thread IDs
|
||||
- **Draft Support**: Create drafts for human-in-the-loop approval workflows
|
||||
- **Label Support**: Organize messages with custom labels
|
||||
- **Pagination**: Efficiently handle large inboxes with cursor-based pagination
|
||||
- **Type-Safe**: Full TypeScript types for all API responses
|
||||
|
||||
## API Documentation
|
||||
|
||||
For detailed API documentation, visit [AgentMail API Docs](https://docs.agentmail.to).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
109
packages/tools/official/agentmail/package.json
Normal file
109
packages/tools/official/agentmail/package.json
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"name": "@tpmjs/tools-agentmail",
|
||||
"version": "0.1.0",
|
||||
"description": "AgentMail API tools for AI agents — create inboxes, send/receive emails, manage threads, drafts, and more",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"tpmjs",
|
||||
"ops",
|
||||
"ai",
|
||||
"email",
|
||||
"agentmail"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rm -rf dist .turbo"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tpmjs/tsconfig": "workspace:*",
|
||||
"tsup": "^8.5.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ai": "6.0.49"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tpmjs/tpmjs.git",
|
||||
"directory": "packages/tools/official/agentmail"
|
||||
},
|
||||
"homepage": "https://tpmjs.com",
|
||||
"license": "MIT",
|
||||
"tpmjs": {
|
||||
"category": "ops",
|
||||
"frameworks": [
|
||||
"vercel-ai"
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"name": "AGENTMAIL_API_KEY",
|
||||
"description": "AgentMail API key from https://app.agentmail.to",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"tools": [
|
||||
{
|
||||
"name": "createInbox",
|
||||
"description": "Create a new email inbox for an AI agent with optional custom username and domain."
|
||||
},
|
||||
{
|
||||
"name": "listInboxes",
|
||||
"description": "List all email inboxes in the AgentMail account with pagination support."
|
||||
},
|
||||
{
|
||||
"name": "getInbox",
|
||||
"description": "Get details of a specific email inbox by its inbox ID."
|
||||
},
|
||||
{
|
||||
"name": "deleteInbox",
|
||||
"description": "Delete an email inbox and all its messages permanently."
|
||||
},
|
||||
{
|
||||
"name": "sendMessage",
|
||||
"description": "Send an email message from an inbox to a recipient with subject and body."
|
||||
},
|
||||
{
|
||||
"name": "replyToMessage",
|
||||
"description": "Reply to an existing email message in a thread conversation."
|
||||
},
|
||||
{
|
||||
"name": "listMessages",
|
||||
"description": "List email messages in an inbox with pagination support."
|
||||
},
|
||||
{
|
||||
"name": "getMessage",
|
||||
"description": "Get the full details of a specific email message by its message ID."
|
||||
},
|
||||
{
|
||||
"name": "listThreads",
|
||||
"description": "List email threads in an inbox, optionally filtered by labels."
|
||||
},
|
||||
{
|
||||
"name": "getThread",
|
||||
"description": "Get a thread with all its messages for viewing a full conversation."
|
||||
},
|
||||
{
|
||||
"name": "createDraft",
|
||||
"description": "Create an email draft for human-in-the-loop approval before sending."
|
||||
},
|
||||
{
|
||||
"name": "sendDraft",
|
||||
"description": "Send a previously created draft, converting it to a sent message."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
751
packages/tools/official/agentmail/src/index.ts
Normal file
751
packages/tools/official/agentmail/src/index.ts
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
/**
|
||||
* @tpmjs/tools-agentmail — AgentMail API Tools for AI Agents
|
||||
*
|
||||
* Complete email management for AI agents: create inboxes, send/receive emails,
|
||||
* manage threads, drafts, and more.
|
||||
*
|
||||
* @requires AGENTMAIL_API_KEY environment variable
|
||||
*/
|
||||
|
||||
import { jsonSchema, tool } from 'ai';
|
||||
|
||||
const BASE_URL = 'https://api.agentmail.to/v0';
|
||||
|
||||
// ─── Client Infrastructure ──────────────────────────────────────────────────
|
||||
|
||||
function getApiKey(): string {
|
||||
const key = process.env.AGENTMAIL_API_KEY;
|
||||
if (!key) {
|
||||
throw new Error(
|
||||
'AGENTMAIL_API_KEY environment variable is required. Get your token from https://app.agentmail.to'
|
||||
);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
async function apiRequest<T>(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
||||
path: string,
|
||||
body?: unknown
|
||||
): Promise<T> {
|
||||
const token = getApiKey();
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const options: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (body !== undefined) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(`${BASE_URL}${path}`, options);
|
||||
|
||||
// Handle 204 No Content for DELETE operations
|
||||
if (response.status === 204) {
|
||||
return { success: true } as T;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
`AgentMail HTTP error ${response.status}: ${response.statusText}${errorText ? ` - ${errorText}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
// ─── Output Types ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface InboxResult {
|
||||
inbox_id: string;
|
||||
pod_id: string;
|
||||
display_name?: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ListInboxesResult {
|
||||
count: number;
|
||||
inboxes: InboxResult[];
|
||||
next_page_token?: string;
|
||||
}
|
||||
|
||||
export interface SuccessResult {
|
||||
success: boolean;
|
||||
inbox_id?: string;
|
||||
}
|
||||
|
||||
export interface MessageResult {
|
||||
message_id: string;
|
||||
thread_id: string;
|
||||
inbox_id: string;
|
||||
from: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
labels: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface ListMessagesResult {
|
||||
count: number;
|
||||
messages: MessageResult[];
|
||||
next_page_token?: string;
|
||||
}
|
||||
|
||||
export interface ThreadResult {
|
||||
thread_id: string;
|
||||
inbox_id: string;
|
||||
subject: string;
|
||||
labels: string[];
|
||||
message_count: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ListThreadsResult {
|
||||
count: number;
|
||||
threads: ThreadResult[];
|
||||
next_page_token?: string;
|
||||
}
|
||||
|
||||
export interface ThreadDetailResult {
|
||||
thread_id: string;
|
||||
inbox_id: string;
|
||||
subject: string;
|
||||
labels: string[];
|
||||
messages: MessageResult[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface DraftResult {
|
||||
draft_id: string;
|
||||
inbox_id: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ─── Inboxes ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateInboxInput {
|
||||
username?: string;
|
||||
domain?: string;
|
||||
display_name?: string;
|
||||
}
|
||||
|
||||
export const createInbox = tool({
|
||||
description:
|
||||
'Create a new email inbox for an AI agent with optional custom username and domain.',
|
||||
inputSchema: jsonSchema<CreateInboxInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
type: 'string',
|
||||
description: 'Optional username for the inbox. If not provided, a random one is generated.',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
description: 'Optional domain (defaults to agentmail.to).',
|
||||
},
|
||||
display_name: {
|
||||
type: 'string',
|
||||
description: 'Optional display name for the inbox.',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: CreateInboxInput): Promise<InboxResult> {
|
||||
try {
|
||||
return await apiRequest<InboxResult>('POST', '/inboxes', {
|
||||
username: input.username,
|
||||
domain: input.domain,
|
||||
display_name: input.display_name,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create inbox: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface ListInboxesInput {
|
||||
limit?: number;
|
||||
page_token?: string;
|
||||
}
|
||||
|
||||
export const listInboxes = tool({
|
||||
description: 'List all email inboxes in the AgentMail account with pagination support.',
|
||||
inputSchema: jsonSchema<ListInboxesInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of inboxes to return (1-100, default: 50).',
|
||||
},
|
||||
page_token: {
|
||||
type: 'string',
|
||||
description: 'Pagination token from previous response.',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: ListInboxesInput): Promise<ListInboxesResult> {
|
||||
try {
|
||||
if (input.limit !== undefined && (input.limit < 1 || input.limit > 100)) {
|
||||
throw new Error('Limit must be between 1 and 100');
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (input.limit !== undefined) params.append('limit', String(input.limit));
|
||||
if (input.page_token) params.append('page_token', input.page_token);
|
||||
|
||||
const queryString = params.toString();
|
||||
const path = queryString ? `/inboxes?${queryString}` : '/inboxes';
|
||||
|
||||
return await apiRequest<ListInboxesResult>('GET', path);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to list inboxes: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface GetInboxInput {
|
||||
inbox_id: string;
|
||||
}
|
||||
|
||||
export const getInbox = tool({
|
||||
description: 'Get details of a specific email inbox by its inbox ID.',
|
||||
inputSchema: jsonSchema<GetInboxInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID to retrieve.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: GetInboxInput): Promise<InboxResult> {
|
||||
try {
|
||||
if (!input.inbox_id) {
|
||||
throw new Error('inbox_id is required and must be non-empty');
|
||||
}
|
||||
return await apiRequest<InboxResult>('GET', `/inboxes/${input.inbox_id}`);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get inbox: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface DeleteInboxInput {
|
||||
inbox_id: string;
|
||||
}
|
||||
|
||||
export const deleteInbox = tool({
|
||||
description: 'Delete an email inbox and all its messages permanently.',
|
||||
inputSchema: jsonSchema<DeleteInboxInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID to delete.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: DeleteInboxInput): Promise<SuccessResult> {
|
||||
try {
|
||||
if (!input.inbox_id) {
|
||||
throw new Error('inbox_id is required and must be non-empty');
|
||||
}
|
||||
await apiRequest<SuccessResult>('DELETE', `/inboxes/${input.inbox_id}`);
|
||||
return { success: true, inbox_id: input.inbox_id };
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to delete inbox: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Messages ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SendMessageInput {
|
||||
inbox_id: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
cc?: string;
|
||||
bcc?: string;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
export const sendMessage = tool({
|
||||
description: 'Send an email message from an inbox to a recipient with subject and body.',
|
||||
inputSchema: jsonSchema<SendMessageInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID to send from.',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
description: 'Recipient email address.',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
description: 'Email subject line.',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'Plain text email body.',
|
||||
},
|
||||
html: {
|
||||
type: 'string',
|
||||
description: 'Optional HTML email body.',
|
||||
},
|
||||
cc: {
|
||||
type: 'string',
|
||||
description: 'Optional CC recipients (comma-separated).',
|
||||
},
|
||||
bcc: {
|
||||
type: 'string',
|
||||
description: 'Optional BCC recipients (comma-separated).',
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Optional labels to apply to the message.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id', 'to', 'subject', 'text'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: SendMessageInput): Promise<MessageResult> {
|
||||
try {
|
||||
if (!input.inbox_id || !input.to || !input.subject || !input.text) {
|
||||
throw new Error('inbox_id, to, subject, and text are required and must be non-empty');
|
||||
}
|
||||
return await apiRequest<MessageResult>('POST', `/inboxes/${input.inbox_id}/messages`, {
|
||||
to: input.to,
|
||||
subject: input.subject,
|
||||
text: input.text,
|
||||
html: input.html,
|
||||
cc: input.cc,
|
||||
bcc: input.bcc,
|
||||
labels: input.labels,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to send message: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface ReplyToMessageInput {
|
||||
inbox_id: string;
|
||||
message_id: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
export const replyToMessage = tool({
|
||||
description: 'Reply to an existing email message in a thread conversation.',
|
||||
inputSchema: jsonSchema<ReplyToMessageInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID.',
|
||||
},
|
||||
message_id: {
|
||||
type: 'string',
|
||||
description: 'The message ID to reply to.',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'Plain text reply body.',
|
||||
},
|
||||
html: {
|
||||
type: 'string',
|
||||
description: 'Optional HTML reply body.',
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Optional labels to apply to the reply.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id', 'message_id', 'text'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: ReplyToMessageInput): Promise<MessageResult> {
|
||||
try {
|
||||
if (!input.inbox_id || !input.message_id || !input.text) {
|
||||
throw new Error('inbox_id, message_id, and text are required and must be non-empty');
|
||||
}
|
||||
return await apiRequest<MessageResult>(
|
||||
'POST',
|
||||
`/inboxes/${input.inbox_id}/messages/${input.message_id}/reply`,
|
||||
{
|
||||
text: input.text,
|
||||
html: input.html,
|
||||
labels: input.labels,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to reply to message: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface ListMessagesInput {
|
||||
inbox_id: string;
|
||||
limit?: number;
|
||||
page_token?: string;
|
||||
}
|
||||
|
||||
export const listMessages = tool({
|
||||
description: 'List email messages in an inbox with pagination support.',
|
||||
inputSchema: jsonSchema<ListMessagesInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID to list messages from.',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of messages to return (1-100, default: 50).',
|
||||
},
|
||||
page_token: {
|
||||
type: 'string',
|
||||
description: 'Pagination token from previous response.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: ListMessagesInput): Promise<ListMessagesResult> {
|
||||
try {
|
||||
if (!input.inbox_id) {
|
||||
throw new Error('inbox_id is required and must be non-empty');
|
||||
}
|
||||
if (input.limit !== undefined && (input.limit < 1 || input.limit > 100)) {
|
||||
throw new Error('Limit must be between 1 and 100');
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (input.limit !== undefined) params.append('limit', String(input.limit));
|
||||
if (input.page_token) params.append('page_token', input.page_token);
|
||||
|
||||
const queryString = params.toString();
|
||||
const path = queryString
|
||||
? `/inboxes/${input.inbox_id}/messages?${queryString}`
|
||||
: `/inboxes/${input.inbox_id}/messages`;
|
||||
|
||||
return await apiRequest<ListMessagesResult>('GET', path);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to list messages: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface GetMessageInput {
|
||||
inbox_id: string;
|
||||
message_id: string;
|
||||
}
|
||||
|
||||
export const getMessage = tool({
|
||||
description: 'Get the full details of a specific email message by its message ID.',
|
||||
inputSchema: jsonSchema<GetMessageInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID.',
|
||||
},
|
||||
message_id: {
|
||||
type: 'string',
|
||||
description: 'The message ID to retrieve.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id', 'message_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: GetMessageInput): Promise<MessageResult> {
|
||||
if (!input.inbox_id) {
|
||||
throw new Error('inbox_id is required and must be non-empty');
|
||||
}
|
||||
if (!input.message_id) {
|
||||
throw new Error('message_id is required and must be non-empty');
|
||||
}
|
||||
|
||||
try {
|
||||
return await apiRequest<MessageResult>(
|
||||
'GET',
|
||||
`/inboxes/${encodeURIComponent(input.inbox_id)}/messages/${encodeURIComponent(input.message_id)}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to get message "${input.message_id}" from inbox "${input.inbox_id}": ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Threads ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ListThreadsInput {
|
||||
inbox_id: string;
|
||||
limit?: number;
|
||||
page_token?: string;
|
||||
labels?: string[];
|
||||
}
|
||||
|
||||
export const listThreads = tool({
|
||||
description: 'List email threads in an inbox, optionally filtered by labels.',
|
||||
inputSchema: jsonSchema<ListThreadsInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID to list threads from.',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of threads to return (1-100, default: 50).',
|
||||
},
|
||||
page_token: {
|
||||
type: 'string',
|
||||
description: 'Pagination token from previous response.',
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Optional labels to filter threads by.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: ListThreadsInput): Promise<ListThreadsResult> {
|
||||
if (!input.inbox_id) {
|
||||
throw new Error('inbox_id is required and must be non-empty');
|
||||
}
|
||||
if (input.limit !== undefined && (input.limit < 1 || input.limit > 100)) {
|
||||
throw new Error('Limit must be between 1 and 100');
|
||||
}
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (input.limit !== undefined) params.append('limit', String(input.limit));
|
||||
if (input.page_token) params.append('page_token', input.page_token);
|
||||
if (Array.isArray(input.labels)) {
|
||||
const validLabels = input.labels.filter((label) => typeof label === 'string' && label.trim().length > 0);
|
||||
for (const label of validLabels) {
|
||||
params.append('labels', label);
|
||||
}
|
||||
}
|
||||
|
||||
const queryString = params.toString();
|
||||
const path = queryString
|
||||
? `/inboxes/${encodeURIComponent(input.inbox_id)}/threads?${queryString}`
|
||||
: `/inboxes/${encodeURIComponent(input.inbox_id)}/threads`;
|
||||
|
||||
return await apiRequest<ListThreadsResult>('GET', path);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to list threads for inbox "${input.inbox_id}": ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface GetThreadInput {
|
||||
inbox_id: string;
|
||||
thread_id: string;
|
||||
}
|
||||
|
||||
export const getThread = tool({
|
||||
description: 'Get a thread with all its messages for viewing a full conversation.',
|
||||
inputSchema: jsonSchema<GetThreadInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID.',
|
||||
},
|
||||
thread_id: {
|
||||
type: 'string',
|
||||
description: 'The thread ID to retrieve.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id', 'thread_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: GetThreadInput): Promise<ThreadDetailResult> {
|
||||
try {
|
||||
if (!input.inbox_id || !input.thread_id) {
|
||||
throw new Error('inbox_id and thread_id are required and must be non-empty');
|
||||
}
|
||||
return await apiRequest<ThreadDetailResult>(
|
||||
'GET',
|
||||
`/inboxes/${input.inbox_id}/threads/${input.thread_id}`
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get thread: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Drafts ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CreateDraftInput {
|
||||
inbox_id: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
cc?: string;
|
||||
bcc?: string;
|
||||
}
|
||||
|
||||
export const createDraft = tool({
|
||||
description: 'Create an email draft for human-in-the-loop approval before sending.',
|
||||
inputSchema: jsonSchema<CreateDraftInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID to create the draft in.',
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
description: 'Recipient email address.',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
description: 'Email subject line.',
|
||||
},
|
||||
text: {
|
||||
type: 'string',
|
||||
description: 'Plain text email body.',
|
||||
},
|
||||
html: {
|
||||
type: 'string',
|
||||
description: 'Optional HTML email body.',
|
||||
},
|
||||
cc: {
|
||||
type: 'string',
|
||||
description: 'Optional CC recipients (comma-separated).',
|
||||
},
|
||||
bcc: {
|
||||
type: 'string',
|
||||
description: 'Optional BCC recipients (comma-separated).',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id', 'to', 'subject', 'text'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: CreateDraftInput): Promise<DraftResult> {
|
||||
try {
|
||||
if (!input.inbox_id || !input.to || !input.subject || !input.text) {
|
||||
throw new Error('inbox_id, to, subject, and text are required and must be non-empty');
|
||||
}
|
||||
return await apiRequest<DraftResult>('POST', `/inboxes/${input.inbox_id}/drafts`, {
|
||||
to: input.to,
|
||||
subject: input.subject,
|
||||
text: input.text,
|
||||
html: input.html,
|
||||
cc: input.cc,
|
||||
bcc: input.bcc,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create draft: ${(error as Error).message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export interface SendDraftInput {
|
||||
inbox_id: string;
|
||||
draft_id: string;
|
||||
}
|
||||
|
||||
export const sendDraft = tool({
|
||||
description: 'Send a previously created draft, converting it to a sent message.',
|
||||
inputSchema: jsonSchema<SendDraftInput>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
inbox_id: {
|
||||
type: 'string',
|
||||
description: 'The inbox ID.',
|
||||
},
|
||||
draft_id: {
|
||||
type: 'string',
|
||||
description: 'The draft ID to send.',
|
||||
},
|
||||
},
|
||||
required: ['inbox_id', 'draft_id'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
async execute(input: SendDraftInput): Promise<MessageResult> {
|
||||
if (!input.inbox_id) {
|
||||
throw new Error('inbox_id is required and must be non-empty');
|
||||
}
|
||||
if (!input.draft_id) {
|
||||
throw new Error('draft_id is required and must be non-empty');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiRequest<MessageResult>(
|
||||
'POST',
|
||||
`/inboxes/${encodeURIComponent(input.inbox_id)}/drafts/${encodeURIComponent(input.draft_id)}/send`
|
||||
);
|
||||
|
||||
if (!response || !response.message_id) {
|
||||
throw new Error('AgentMail API returned an invalid response when sending draft');
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to send draft "${input.draft_id}" from inbox "${input.inbox_id}": ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ─── Default Export ─────────────────────────────────────────────────────────
|
||||
|
||||
export default {
|
||||
// Inboxes
|
||||
createInbox,
|
||||
listInboxes,
|
||||
getInbox,
|
||||
deleteInbox,
|
||||
// Messages
|
||||
sendMessage,
|
||||
replyToMessage,
|
||||
listMessages,
|
||||
getMessage,
|
||||
// Threads
|
||||
listThreads,
|
||||
getThread,
|
||||
// Drafts
|
||||
createDraft,
|
||||
sendDraft,
|
||||
};
|
||||
11
packages/tools/official/agentmail/tsconfig.json
Normal file
11
packages/tools/official/agentmail/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "@tpmjs/tsconfig/react-library.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"incremental": false,
|
||||
"composite": false
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
12
packages/tools/official/agentmail/tsup.config.ts
Normal file
12
packages/tools/official/agentmail/tsup.config.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
target: 'es2022',
|
||||
treeshake: true,
|
||||
splitting: false,
|
||||
});
|
||||
|
|
@ -13105,6 +13105,288 @@ blocks:
|
|||
description: "Pin confirmation"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AgentMail Tools (email for AI agents)
|
||||
# ---------------------------------------------------------------------------
|
||||
ops.agentmailCreateInbox:
|
||||
type: utility
|
||||
description: "Create a new email inbox for an AI agent."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API POST /v0/inboxes endpoint"
|
||||
inputs:
|
||||
- name: username
|
||||
type: string
|
||||
optional: true
|
||||
description: "Username for the email address"
|
||||
- name: domain
|
||||
type: string
|
||||
optional: true
|
||||
description: "Domain for the email address"
|
||||
- name: display_name
|
||||
type: string
|
||||
optional: true
|
||||
description: "Display name for the inbox"
|
||||
outputs:
|
||||
- name: inbox
|
||||
type: InboxResult
|
||||
description: "Created inbox details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailListInboxes:
|
||||
type: utility
|
||||
description: "List all email inboxes in the account."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API GET /v0/inboxes endpoint"
|
||||
inputs:
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max inboxes to return (1-100)"
|
||||
- name: page_token
|
||||
type: string
|
||||
optional: true
|
||||
description: "Pagination token"
|
||||
outputs:
|
||||
- name: result
|
||||
type: ListInboxesResult
|
||||
description: "List of inboxes with pagination"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailGetInbox:
|
||||
type: utility
|
||||
description: "Get details of a specific email inbox."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id} endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
outputs:
|
||||
- name: inbox
|
||||
type: InboxResult
|
||||
description: "Inbox details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailDeleteInbox:
|
||||
type: utility
|
||||
description: "Delete an email inbox and all its messages."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API DELETE /v0/inboxes/{inbox_id} endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
outputs:
|
||||
- name: result
|
||||
type: SuccessResult
|
||||
description: "Deletion confirmation"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailSendMessage:
|
||||
type: utility
|
||||
description: "Send an email message from an inbox."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/messages endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID to send from"
|
||||
- name: to
|
||||
type: string
|
||||
description: "Recipient email address"
|
||||
- name: subject
|
||||
type: string
|
||||
description: "Email subject"
|
||||
- name: text
|
||||
type: string
|
||||
description: "Plain text body"
|
||||
- name: html
|
||||
type: string
|
||||
optional: true
|
||||
description: "HTML body"
|
||||
outputs:
|
||||
- name: message
|
||||
type: MessageResult
|
||||
description: "Sent message details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailReplyToMessage:
|
||||
type: utility
|
||||
description: "Reply to an existing email message in a thread."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/messages/{message_id}/reply endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: message_id
|
||||
type: string
|
||||
description: "Message ID to reply to"
|
||||
- name: text
|
||||
type: string
|
||||
description: "Reply text"
|
||||
- name: html
|
||||
type: string
|
||||
optional: true
|
||||
description: "Reply HTML body"
|
||||
outputs:
|
||||
- name: message
|
||||
type: MessageResult
|
||||
description: "Reply message details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailListMessages:
|
||||
type: utility
|
||||
description: "List email messages in an inbox."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/messages endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max messages to return (1-100)"
|
||||
- name: page_token
|
||||
type: string
|
||||
optional: true
|
||||
description: "Pagination token"
|
||||
outputs:
|
||||
- name: result
|
||||
type: ListMessagesResult
|
||||
description: "List of messages with pagination"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailGetMessage:
|
||||
type: utility
|
||||
description: "Get the full details of a specific email message."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/messages/{message_id} endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: message_id
|
||||
type: string
|
||||
description: "Message ID"
|
||||
outputs:
|
||||
- name: message
|
||||
type: MessageResult
|
||||
description: "Full message details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailListThreads:
|
||||
type: utility
|
||||
description: "List email threads in an inbox, optionally filtered by labels."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/threads endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: limit
|
||||
type: number
|
||||
optional: true
|
||||
description: "Max threads to return (1-100)"
|
||||
- name: labels
|
||||
type: array
|
||||
optional: true
|
||||
description: "Filter by labels"
|
||||
outputs:
|
||||
- name: result
|
||||
type: ListThreadsResult
|
||||
description: "List of threads with pagination"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailGetThread:
|
||||
type: utility
|
||||
description: "Get a thread with all its messages for viewing a full conversation."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/threads/{thread_id} endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: thread_id
|
||||
type: string
|
||||
description: "Thread ID"
|
||||
outputs:
|
||||
- name: thread
|
||||
type: ThreadDetailResult
|
||||
description: "Thread with all messages"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailCreateDraft:
|
||||
type: utility
|
||||
description: "Create an email draft for human-in-the-loop approval."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/drafts endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: to
|
||||
type: string
|
||||
description: "Recipient email"
|
||||
- name: subject
|
||||
type: string
|
||||
description: "Email subject"
|
||||
- name: text
|
||||
type: string
|
||||
description: "Plain text body"
|
||||
- name: html
|
||||
type: string
|
||||
optional: true
|
||||
description: "HTML body"
|
||||
outputs:
|
||||
- name: draft
|
||||
type: DraftResult
|
||||
description: "Created draft details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
ops.agentmailSendDraft:
|
||||
type: utility
|
||||
description: "Send a previously created draft, converting it to a sent message."
|
||||
path: "agentmail"
|
||||
domain_rules:
|
||||
- id: api_integration
|
||||
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/drafts/{draft_id}/send endpoint"
|
||||
inputs:
|
||||
- name: inbox_id
|
||||
type: string
|
||||
description: "Inbox ID"
|
||||
- name: draft_id
|
||||
type: string
|
||||
description: "Draft ID"
|
||||
outputs:
|
||||
- name: message
|
||||
type: MessageResult
|
||||
description: "Sent message details"
|
||||
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
|
||||
|
||||
# =============================================================================
|
||||
# VALIDATORS - Which validators to run against each block
|
||||
# =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue