chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
import type { OutlookCopyParams, OutlookCopyResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookCopyTool: ToolConfig<OutlookCopyParams, OutlookCopyResponse> = {
id: 'outlook_copy',
name: 'Outlook Copy',
description: 'Copy an Outlook message to another folder',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to copy',
},
destinationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the destination folder',
},
},
request: {
url: '/api/tools/outlook/copy',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookCopyParams) => ({
accessToken: params.accessToken,
messageId: params.messageId,
destinationId: params.destinationId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to copy Outlook email')
}
return {
success: true,
output: {
message: data.output.message,
results: {
originalMessageId: data.output.originalMessageId,
copiedMessageId: data.output.copiedMessageId,
destinationFolderId: data.output.destinationFolderId,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Email copy success status' },
message: { type: 'string', description: 'Success or error message' },
originalMessageId: { type: 'string', description: 'ID of the original message' },
copiedMessageId: { type: 'string', description: 'ID of the copied message' },
destinationFolderId: { type: 'string', description: 'ID of the destination folder' },
},
}
+91
View File
@@ -0,0 +1,91 @@
import type { OutlookCreateFolderParams, OutlookCreateFolderResponse } from '@/tools/outlook/types'
import { OUTLOOK_FOLDER_OUTPUT_PROPERTIES } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookCreateFolderTool: ToolConfig<
OutlookCreateFolderParams,
OutlookCreateFolderResponse
> = {
id: 'outlook_create_folder',
name: 'Outlook Create Folder',
description: 'Create a new mail folder in the root of the Outlook mailbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
displayName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The display name of the new folder',
},
isHidden: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the new folder is hidden (cannot be changed after creation)',
},
},
request: {
url: () => 'https://graph.microsoft.com/v1.0/me/mailFolders',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const displayName = params.displayName?.trim()
if (!displayName) {
throw new Error('A folder display name is required')
}
return {
displayName,
...(params.isHidden ? { isHidden: true } : {}),
}
},
},
transformResponse: async (response: Response) => {
const folder = await response.json()
return {
success: true,
output: {
message: `Successfully created folder "${folder.displayName ?? ''}".`,
results: {
id: folder.id,
displayName: folder.displayName ?? null,
parentFolderId: folder.parentFolderId ?? null,
childFolderCount: folder.childFolderCount ?? null,
unreadItemCount: folder.unreadItemCount ?? null,
totalItemCount: folder.totalItemCount ?? null,
isHidden: folder.isHidden ?? null,
},
},
}
},
outputs: {
message: { type: 'string', description: 'Success or status message' },
results: {
type: 'object',
description: 'The newly created mail folder',
properties: OUTLOOK_FOLDER_OUTPUT_PROPERTIES,
},
},
}
+65
View File
@@ -0,0 +1,65 @@
import type { OutlookDeleteParams, OutlookDeleteResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookDeleteTool: ToolConfig<OutlookDeleteParams, OutlookDeleteResponse> = {
id: 'outlook_delete',
name: 'Outlook Delete',
description: 'Delete an Outlook message (move to Deleted Items)',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to delete',
},
},
request: {
url: '/api/tools/outlook/delete',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookDeleteParams) => ({
accessToken: params.accessToken,
messageId: params.messageId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to delete Outlook email')
}
return {
success: true,
output: {
message: data.output.message,
results: {
messageId: data.output.messageId,
status: data.output.status,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
message: { type: 'string', description: 'Success or error message' },
messageId: { type: 'string', description: 'ID of the deleted message' },
status: { type: 'string', description: 'Deletion status' },
},
}
+114
View File
@@ -0,0 +1,114 @@
import type { OutlookDraftParams, OutlookDraftResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookDraftTool: ToolConfig<OutlookDraftParams, OutlookDraftResponse> = {
id: 'outlook_draft',
name: 'Outlook Draft',
description: 'Draft emails using Outlook',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Recipient email address',
},
subject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email subject',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email body content',
},
contentType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Content type for the email body (text or html)',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC recipients (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC recipients (comma-separated)',
},
attachments: {
type: 'file[]',
required: false,
visibility: 'user-only',
description: 'Files to attach to the email draft',
},
},
request: {
url: '/api/tools/outlook/draft',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookDraftParams) => {
return {
accessToken: params.accessToken,
to: params.to,
subject: params.subject,
body: params.body,
contentType: params.contentType || 'text',
cc: params.cc || null,
bcc: params.bcc || null,
attachments: params.attachments || null,
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to create Outlook draft')
}
return {
success: true,
output: {
message: data.output.message,
results: {
id: data.output.messageId,
subject: data.output.subject,
status: 'drafted',
timestamp: new Date().toISOString(),
attachmentCount: data.output.attachmentCount || 0,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Email draft creation success status' },
messageId: { type: 'string', description: 'Unique identifier for the drafted email' },
status: { type: 'string', description: 'Draft status of the email' },
subject: { type: 'string', description: 'Subject of the drafted email' },
timestamp: { type: 'string', description: 'Timestamp when draft was created' },
message: { type: 'string', description: 'Success or error message' },
},
}
+154
View File
@@ -0,0 +1,154 @@
import type { OutlookForwardParams, OutlookForwardResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookForwardTool: ToolConfig<OutlookForwardParams, OutlookForwardResponse> = {
id: 'outlook_forward',
name: 'Outlook Forward',
description: 'Forward an existing Outlook message to specified recipients',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the message to forward',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Recipient email address(es), comma-separated',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional comment to include with the forwarded message',
},
},
request: {
url: (params) => {
return `https://graph.microsoft.com/v1.0/me/messages/${params.messageId}/forward`
},
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params: OutlookForwardParams): Record<string, any> => {
const parseEmails = (emailString?: string) => {
if (!emailString) return []
return emailString
.split(',')
.map((email) => email.trim())
.filter((email) => email.length > 0)
.map((email) => ({ emailAddress: { address: email } }))
}
const toRecipients = parseEmails(params.to)
if (toRecipients.length === 0) {
throw new Error('At least one recipient is required to forward a message')
}
return {
comment: params.comment ?? '',
toRecipients,
}
},
},
transformResponse: async (response: Response) => {
const status = response.status
const requestId =
response.headers?.get('request-id') || response.headers?.get('x-ms-request-id') || undefined
// Graph forward action typically returns 202/204 with no body. Try to read text safely.
let bodyText = ''
try {
bodyText = await response.text()
} catch (_) {
// ignore body read errors
}
// Attempt to parse JSON if present (rare for this endpoint). Extract message identifiers if available.
let parsed: any | undefined
if (bodyText && bodyText.trim().length > 0) {
try {
parsed = JSON.parse(bodyText)
} catch (_) {
// non-JSON body; ignore
}
}
const messageId = parsed?.id || parsed?.messageId || parsed?.internetMessageId
const internetMessageId = parsed?.internetMessageId
return {
success: true,
output: {
message:
status === 202 || status === 204
? 'Email forwarded successfully'
: `Email forwarded (HTTP ${status})`,
results: {
status: 'forwarded',
timestamp: new Date().toISOString(),
httpStatus: status,
requestId,
...(messageId ? { messageId } : {}),
...(internetMessageId ? { internetMessageId } : {}),
},
},
}
},
outputs: {
message: { type: 'string', description: 'Success or error message' },
results: {
type: 'object',
description: 'Delivery result details',
properties: {
status: { type: 'string', description: 'Delivery status of the email' },
timestamp: { type: 'string', description: 'Timestamp when email was forwarded' },
httpStatus: {
type: 'number',
description: 'HTTP status code returned by the API',
optional: true,
},
requestId: {
type: 'string',
description: 'Microsoft Graph request-id header for tracing',
optional: true,
},
messageId: {
type: 'string',
description: 'Forwarded message ID if provided by API',
optional: true,
},
internetMessageId: {
type: 'string',
description: 'RFC 822 Message-ID if provided',
optional: true,
},
},
},
},
}
+122
View File
@@ -0,0 +1,122 @@
import type {
OutlookAttachment,
OutlookGetAttachmentParams,
OutlookGetAttachmentResponse,
} from '@/tools/outlook/types'
import {
OUTLOOK_ATTACHMENT_METADATA_OUTPUT_PROPERTIES,
OUTLOOK_ATTACHMENT_OUTPUT_PROPERTIES,
} from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
interface OutlookAttachmentApi {
'@odata.type'?: string
id: string
name?: string
contentType?: string
size?: number
isInline?: boolean
lastModifiedDateTime?: string
contentBytes?: string
}
export const outlookGetAttachmentTool: ToolConfig<
OutlookGetAttachmentParams,
OutlookGetAttachmentResponse
> = {
id: 'outlook_get_attachment',
name: 'Outlook Get Attachment',
description: 'Get a single attachment on an Outlook message, including its file contents',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the message that owns the attachment',
},
attachmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the attachment to retrieve',
},
},
request: {
url: (params) =>
`https://graph.microsoft.com/v1.0/me/messages/${params.messageId.trim()}/attachments/${params.attachmentId.trim()}`,
method: 'GET',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response) => {
const attachment: OutlookAttachmentApi = await response.json()
const files: OutlookAttachment[] = []
if (
attachment['@odata.type'] === '#microsoft.graph.fileAttachment' &&
attachment.contentBytes
) {
files.push({
name: attachment.name ?? 'attachment',
data: attachment.contentBytes,
contentType: attachment.contentType ?? 'application/octet-stream',
size: attachment.size ?? 0,
})
}
return {
success: true,
output: {
message: `Successfully retrieved attachment "${attachment.name ?? ''}".`,
results: {
id: attachment.id,
name: attachment.name ?? null,
contentType: attachment.contentType ?? null,
size: attachment.size ?? null,
isInline: attachment.isInline ?? null,
attachmentType: attachment['@odata.type'] ?? null,
lastModifiedDateTime: attachment.lastModifiedDateTime ?? null,
},
attachments: files,
},
}
},
outputs: {
message: { type: 'string', description: 'Success or status message' },
results: {
type: 'object',
description: 'Attachment metadata',
properties: OUTLOOK_ATTACHMENT_METADATA_OUTPUT_PROPERTIES,
},
attachments: {
type: 'file[]',
description: 'The downloaded file attachment (empty for non-file attachment types)',
items: {
type: 'object',
properties: OUTLOOK_ATTACHMENT_OUTPUT_PROPERTIES,
},
},
},
}
+37
View File
@@ -0,0 +1,37 @@
import { outlookCopyTool } from '@/tools/outlook/copy'
import { outlookCreateFolderTool } from '@/tools/outlook/create_folder'
import { outlookDeleteTool } from '@/tools/outlook/delete'
import { outlookDraftTool } from '@/tools/outlook/draft'
import { outlookForwardTool } from '@/tools/outlook/forward'
import { outlookGetAttachmentTool } from '@/tools/outlook/get_attachment'
import { outlookListAttachmentsTool } from '@/tools/outlook/list_attachments'
import { outlookListFoldersTool } from '@/tools/outlook/list_folders'
import { outlookMarkReadTool } from '@/tools/outlook/mark_read'
import { outlookMarkUnreadTool } from '@/tools/outlook/mark_unread'
import { outlookMoveTool } from '@/tools/outlook/move'
import { outlookReadTool } from '@/tools/outlook/read'
import { outlookReplyTool } from '@/tools/outlook/reply'
import { outlookReplyAllTool } from '@/tools/outlook/reply_all'
import { outlookSearchTool } from '@/tools/outlook/search'
import { outlookSendTool } from '@/tools/outlook/send'
import { outlookUpdateMessageTool } from '@/tools/outlook/update_message'
export {
outlookCopyTool,
outlookCreateFolderTool,
outlookDeleteTool,
outlookDraftTool,
outlookForwardTool,
outlookGetAttachmentTool,
outlookListAttachmentsTool,
outlookListFoldersTool,
outlookMarkReadTool,
outlookMarkUnreadTool,
outlookMoveTool,
outlookReadTool,
outlookReplyTool,
outlookReplyAllTool,
outlookSearchTool,
outlookSendTool,
outlookUpdateMessageTool,
}
@@ -0,0 +1,98 @@
import type {
CleanedOutlookAttachmentMetadata,
OutlookListAttachmentsParams,
OutlookListAttachmentsResponse,
} from '@/tools/outlook/types'
import { OUTLOOK_ATTACHMENT_METADATA_OUTPUT_PROPERTIES } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
interface OutlookAttachmentApi {
'@odata.type'?: string
id: string
name?: string
contentType?: string
size?: number
isInline?: boolean
lastModifiedDateTime?: string
}
export const outlookListAttachmentsTool: ToolConfig<
OutlookListAttachmentsParams,
OutlookListAttachmentsResponse
> = {
id: 'outlook_list_attachments',
name: 'Outlook List Attachments',
description: 'List the attachments on an Outlook message (metadata only, without contents)',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the message whose attachments to list',
},
},
request: {
url: (params) =>
`https://graph.microsoft.com/v1.0/me/messages/${params.messageId.trim()}/attachments?$select=id,name,contentType,size,isInline,lastModifiedDateTime`,
method: 'GET',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const attachments: OutlookAttachmentApi[] = data.value || []
const cleanedAttachments: CleanedOutlookAttachmentMetadata[] = attachments.map(
(attachment) => ({
id: attachment.id,
name: attachment.name ?? null,
contentType: attachment.contentType ?? null,
size: attachment.size ?? null,
isInline: attachment.isInline ?? null,
attachmentType: attachment['@odata.type'] ?? null,
lastModifiedDateTime: attachment.lastModifiedDateTime ?? null,
})
)
return {
success: true,
output: {
message: `Successfully retrieved ${cleanedAttachments.length} attachment(s).`,
results: cleanedAttachments,
},
}
},
outputs: {
message: { type: 'string', description: 'Success or status message' },
results: {
type: 'array',
description: 'Array of attachment metadata objects',
items: {
type: 'object',
properties: OUTLOOK_ATTACHMENT_METADATA_OUTPUT_PROPERTIES,
},
},
},
}
+110
View File
@@ -0,0 +1,110 @@
import type {
CleanedOutlookFolder,
OutlookListFoldersParams,
OutlookListFoldersResponse,
} from '@/tools/outlook/types'
import { OUTLOOK_FOLDER_OUTPUT_PROPERTIES } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
interface OutlookFolderApi {
id: string
displayName?: string
parentFolderId?: string
childFolderCount?: number
unreadItemCount?: number
totalItemCount?: number
isHidden?: boolean
}
export const outlookListFoldersTool: ToolConfig<
OutlookListFoldersParams,
OutlookListFoldersResponse
> = {
id: 'outlook_list_folders',
name: 'Outlook List Folders',
description: 'List mail folders in the root of the Outlook mailbox',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of folders to retrieve (default: 50, max: 100)',
},
includeHiddenFolders: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include hidden folders in the results',
},
},
request: {
url: (params) => {
const maxResults = params.maxResults
? Math.max(1, Math.min(Math.abs(Number(params.maxResults)), 100))
: 50
const query = new URLSearchParams({ $top: String(maxResults) })
if (params.includeHiddenFolders) {
query.set('includeHiddenFolders', 'true')
}
return `https://graph.microsoft.com/v1.0/me/mailFolders?${query.toString()}`
},
method: 'GET',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const folders: OutlookFolderApi[] = data.value || []
const cleanedFolders: CleanedOutlookFolder[] = folders.map((folder) => ({
id: folder.id,
displayName: folder.displayName ?? null,
parentFolderId: folder.parentFolderId ?? null,
childFolderCount: folder.childFolderCount ?? null,
unreadItemCount: folder.unreadItemCount ?? null,
totalItemCount: folder.totalItemCount ?? null,
isHidden: folder.isHidden ?? null,
}))
return {
success: true,
output: {
message: `Successfully retrieved ${cleanedFolders.length} folder(s).`,
results: cleanedFolders,
},
}
},
outputs: {
message: { type: 'string', description: 'Success or status message' },
results: {
type: 'array',
description: 'Array of mail folder objects',
items: {
type: 'object',
properties: OUTLOOK_FOLDER_OUTPUT_PROPERTIES,
},
},
},
}
+65
View File
@@ -0,0 +1,65 @@
import type { OutlookMarkReadParams, OutlookMarkReadResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookMarkReadTool: ToolConfig<OutlookMarkReadParams, OutlookMarkReadResponse> = {
id: 'outlook_mark_read',
name: 'Outlook Mark as Read',
description: 'Mark an Outlook message as read',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to mark as read',
},
},
request: {
url: '/api/tools/outlook/mark-read',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookMarkReadParams) => ({
accessToken: params.accessToken,
messageId: params.messageId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to mark Outlook email as read')
}
return {
success: true,
output: {
message: data.output.message,
results: {
messageId: data.output.messageId,
isRead: data.output.isRead,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
message: { type: 'string', description: 'Success or error message' },
messageId: { type: 'string', description: 'ID of the message' },
isRead: { type: 'boolean', description: 'Read status of the message' },
},
}
+65
View File
@@ -0,0 +1,65 @@
import type { OutlookMarkReadParams, OutlookMarkReadResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookMarkUnreadTool: ToolConfig<OutlookMarkReadParams, OutlookMarkReadResponse> = {
id: 'outlook_mark_unread',
name: 'Outlook Mark as Unread',
description: 'Mark an Outlook message as unread',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to mark as unread',
},
},
request: {
url: '/api/tools/outlook/mark-unread',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookMarkReadParams) => ({
accessToken: params.accessToken,
messageId: params.messageId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to mark Outlook email as unread')
}
return {
success: true,
output: {
message: data.output.message,
results: {
messageId: data.output.messageId,
isRead: data.output.isRead,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
message: { type: 'string', description: 'Success or error message' },
messageId: { type: 'string', description: 'ID of the message' },
isRead: { type: 'boolean', description: 'Read status of the message' },
},
}
+72
View File
@@ -0,0 +1,72 @@
import type { OutlookMoveParams, OutlookMoveResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookMoveTool: ToolConfig<OutlookMoveParams, OutlookMoveResponse> = {
id: 'outlook_move',
name: 'Outlook Move',
description: 'Move emails between Outlook folders',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the message to move',
},
destinationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the destination folder',
},
},
request: {
url: '/api/tools/outlook/move',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookMoveParams) => ({
accessToken: params.accessToken,
messageId: params.messageId,
destinationId: params.destinationId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to move Outlook email')
}
return {
success: true,
output: {
message: data.output.message,
results: {
messageId: data.output.messageId,
newFolderId: data.output.newFolderId,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Email move success status' },
message: { type: 'string', description: 'Success or error message' },
messageId: { type: 'string', description: 'ID of the moved message' },
newFolderId: { type: 'string', description: 'ID of the destination folder' },
},
}
+228
View File
@@ -0,0 +1,228 @@
import type {
CleanedOutlookMessage,
OutlookAttachment,
OutlookMessage,
OutlookMessagesResponse,
OutlookReadParams,
OutlookReadResponse,
} from '@/tools/outlook/types'
import { OUTLOOK_MESSAGE_OUTPUT_PROPERTIES } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
/**
* Download attachments from an Outlook message
*/
async function downloadAttachments(
messageId: string,
accessToken: string
): Promise<OutlookAttachment[]> {
const attachments: OutlookAttachment[] = []
try {
// Fetch attachments list from Microsoft Graph API
const response = await fetch(
`https://graph.microsoft.com/v1.0/me/messages/${messageId}/attachments`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
)
if (!response.ok) {
return attachments
}
const data = await response.json()
const attachmentsList = data.value || []
for (const attachment of attachmentsList) {
try {
// Microsoft Graph returns attachment data directly in the list response for file attachments
if (attachment['@odata.type'] === '#microsoft.graph.fileAttachment') {
const contentBytes = attachment.contentBytes
if (contentBytes) {
// contentBytes is base64 encoded
const buffer = Buffer.from(contentBytes, 'base64')
attachments.push({
name: attachment.name,
data: buffer.toString('base64'),
contentType: attachment.contentType,
size: attachment.size,
})
}
}
} catch (error) {
// Continue with other attachments
}
}
} catch (error) {
// Return empty array on error
}
return attachments
}
export const outlookReadTool: ToolConfig<OutlookReadParams, OutlookReadResponse> = {
id: 'outlook_read',
name: 'Outlook Read',
description: 'Read emails from Outlook',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
folder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Folder ID to read emails from (e.g., "Inbox", "Drafts", or a folder ID)',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of emails to retrieve (default: 1, max: 10)',
},
includeAttachments: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to download and include email attachments',
},
},
request: {
url: (params) => {
// Set max results (default to 1 for simplicity, max 10) with no negative values
const maxResults = params.maxResults
? Math.max(1, Math.min(Math.abs(Number(params.maxResults)), 10))
: 1
// If folder is provided, read from that specific folder
if (params.folder) {
return `https://graph.microsoft.com/v1.0/me/mailFolders/${params.folder}/messages?$top=${maxResults}&$orderby=createdDateTime desc`
}
// Otherwise fetch from all messages (default behavior)
return `https://graph.microsoft.com/v1.0/me/messages?$top=${maxResults}&$orderby=createdDateTime desc`
},
method: 'GET',
headers: (params) => {
// Validate access token
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response, params?: OutlookReadParams) => {
const data: OutlookMessagesResponse = await response.json()
// Microsoft Graph API returns messages in a 'value' array
const messages = data.value || []
if (messages.length === 0) {
return {
success: true,
output: {
message: 'No mail found.',
results: [],
},
}
}
// Clean up the message data to only include essential fields
const cleanedMessages: CleanedOutlookMessage[] = await Promise.all(
messages.map(async (message: OutlookMessage) => {
// Download attachments if requested
let attachments: OutlookAttachment[] | undefined
if (params?.includeAttachments && message.hasAttachments && params?.accessToken) {
try {
attachments = await downloadAttachments(message.id, params.accessToken)
} catch (error) {
// Continue without attachments rather than failing the entire request
}
}
return {
id: message.id,
subject: message.subject,
bodyPreview: message.bodyPreview,
body: {
contentType: message.body?.contentType,
content: message.body?.content,
},
sender: {
name: message.sender?.emailAddress?.name,
address: message.sender?.emailAddress?.address,
},
from: {
name: message.from?.emailAddress?.name,
address: message.from?.emailAddress?.address,
},
toRecipients:
message.toRecipients?.map((recipient) => ({
name: recipient.emailAddress?.name,
address: recipient.emailAddress?.address,
})) || [],
ccRecipients:
message.ccRecipients?.map((recipient) => ({
name: recipient.emailAddress?.name,
address: recipient.emailAddress?.address,
})) || [],
receivedDateTime: message.receivedDateTime,
sentDateTime: message.sentDateTime,
hasAttachments: message.hasAttachments,
attachments: attachments || [],
isRead: message.isRead,
importance: message.importance,
}
})
)
// Flatten all attachments from all emails to top level for FileToolProcessor
const allAttachments: OutlookAttachment[] = []
for (const email of cleanedMessages) {
if (email.attachments && email.attachments.length > 0) {
allAttachments.push(...email.attachments)
}
}
return {
success: true,
output: {
message: `Successfully read ${cleanedMessages.length} email(s).`,
results: cleanedMessages,
attachments: allAttachments,
},
}
},
outputs: {
message: { type: 'string', description: 'Success or status message' },
results: {
type: 'array',
description: 'Array of email message objects',
items: {
type: 'object',
properties: OUTLOOK_MESSAGE_OUTPUT_PROPERTIES,
},
},
attachments: { type: 'file[]', description: 'All email attachments flattened from all emails' },
},
}
+97
View File
@@ -0,0 +1,97 @@
import type { OutlookReplyParams, OutlookReplyResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookReplyTool: ToolConfig<OutlookReplyParams, OutlookReplyResponse> = {
id: 'outlook_reply',
name: 'Outlook Reply',
description: 'Reply to the sender of an Outlook message with a comment',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the message to reply to',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The reply text to include above the original message',
},
},
request: {
url: (params) =>
`https://graph.microsoft.com/v1.0/me/messages/${params.messageId.trim()}/reply`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
comment: params.comment ?? '',
}),
},
transformResponse: async (response: Response) => {
const status = response.status
const requestId =
response.headers?.get('request-id') || response.headers?.get('x-ms-request-id') || undefined
return {
success: true,
output: {
message:
status === 202 || status === 200
? 'Reply sent successfully'
: `Reply sent (HTTP ${status})`,
results: {
status: 'replied',
timestamp: new Date().toISOString(),
httpStatus: status,
requestId,
},
},
}
},
outputs: {
message: { type: 'string', description: 'Success or error message' },
results: {
type: 'object',
description: 'Reply result details',
properties: {
status: { type: 'string', description: 'Reply status' },
timestamp: { type: 'string', description: 'Timestamp when the reply was sent' },
httpStatus: {
type: 'number',
description: 'HTTP status code returned by the API',
optional: true,
},
requestId: {
type: 'string',
description: 'Microsoft Graph request-id header for tracing',
optional: true,
},
},
},
},
}
+97
View File
@@ -0,0 +1,97 @@
import type { OutlookReplyParams, OutlookReplyResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookReplyAllTool: ToolConfig<OutlookReplyParams, OutlookReplyResponse> = {
id: 'outlook_reply_all',
name: 'Outlook Reply All',
description: 'Reply to all recipients of an Outlook message with a comment',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the message to reply to',
},
comment: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The reply text to include above the original message',
},
},
request: {
url: (params) =>
`https://graph.microsoft.com/v1.0/me/messages/${params.messageId.trim()}/replyAll`,
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => ({
comment: params.comment ?? '',
}),
},
transformResponse: async (response: Response) => {
const status = response.status
const requestId =
response.headers?.get('request-id') || response.headers?.get('x-ms-request-id') || undefined
return {
success: true,
output: {
message:
status === 202 || status === 200
? 'Reply all sent successfully'
: `Reply all sent (HTTP ${status})`,
results: {
status: 'repliedAll',
timestamp: new Date().toISOString(),
httpStatus: status,
requestId,
},
},
}
},
outputs: {
message: { type: 'string', description: 'Success or error message' },
results: {
type: 'object',
description: 'Reply-all result details',
properties: {
status: { type: 'string', description: 'Reply status' },
timestamp: { type: 'string', description: 'Timestamp when the reply was sent' },
httpStatus: {
type: 'number',
description: 'HTTP status code returned by the API',
optional: true,
},
requestId: {
type: 'string',
description: 'Microsoft Graph request-id header for tracing',
optional: true,
},
},
},
},
}
+141
View File
@@ -0,0 +1,141 @@
import type {
CleanedOutlookMessage,
OutlookMessage,
OutlookMessagesResponse,
OutlookSearchParams,
OutlookSearchResponse,
} from '@/tools/outlook/types'
import { OUTLOOK_MESSAGE_OUTPUT_PROPERTIES } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookSearchTool: ToolConfig<OutlookSearchParams, OutlookSearchResponse> = {
id: 'outlook_search',
name: 'Outlook Search',
description: 'Search Outlook messages using a free-text query (Microsoft Graph $search)',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search text matched against the subject, body, sender, and recipients of messages',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of messages to retrieve (default: 10, max: 25)',
},
},
request: {
url: (params) => {
const rawQuery = params.query?.trim()
if (!rawQuery) {
throw new Error('A search query is required')
}
const query = rawQuery.replace(/"/g, ' ').trim()
if (!query) {
throw new Error('A search query is required')
}
const maxResults = params.maxResults
? Math.max(1, Math.min(Math.abs(Number(params.maxResults)), 25))
: 10
const searchParams = new URLSearchParams({
$search: `"${query}"`,
$top: String(maxResults),
})
return `https://graph.microsoft.com/v1.0/me/messages?${searchParams.toString()}`
},
method: 'GET',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
}
},
},
transformResponse: async (response: Response) => {
const data: OutlookMessagesResponse = await response.json()
const messages = data.value || []
if (messages.length === 0) {
return {
success: true,
output: {
message: 'No matching messages found.',
results: [],
},
}
}
const cleanedMessages: CleanedOutlookMessage[] = messages.map((message: OutlookMessage) => ({
id: message.id,
subject: message.subject,
bodyPreview: message.bodyPreview,
body: {
contentType: message.body?.contentType,
content: message.body?.content,
},
sender: {
name: message.sender?.emailAddress?.name,
address: message.sender?.emailAddress?.address,
},
from: {
name: message.from?.emailAddress?.name,
address: message.from?.emailAddress?.address,
},
toRecipients:
message.toRecipients?.map((recipient) => ({
name: recipient.emailAddress?.name,
address: recipient.emailAddress?.address,
})) || [],
ccRecipients:
message.ccRecipients?.map((recipient) => ({
name: recipient.emailAddress?.name,
address: recipient.emailAddress?.address,
})) || [],
receivedDateTime: message.receivedDateTime,
sentDateTime: message.sentDateTime,
hasAttachments: message.hasAttachments,
isRead: message.isRead,
importance: message.importance,
}))
return {
success: true,
output: {
message: `Found ${cleanedMessages.length} matching message(s).`,
results: cleanedMessages,
},
}
},
outputs: {
message: { type: 'string', description: 'Success or status message' },
results: {
type: 'array',
description: 'Array of matching email message objects',
items: {
type: 'object',
properties: OUTLOOK_MESSAGE_OUTPUT_PROPERTIES,
},
},
},
}
+117
View File
@@ -0,0 +1,117 @@
import type { OutlookSendParams, OutlookSendResponse } from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
export const outlookSendTool: ToolConfig<OutlookSendParams, OutlookSendResponse> = {
id: 'outlook_send',
name: 'Outlook Send',
description: 'Send emails using Outlook',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Outlook API',
},
to: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Recipient email address (comma-separated for multiple recipients)',
},
subject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email subject',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email body content',
},
contentType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Content type for the email body (text or html)',
},
replyToMessageId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Message ID to reply to (for threading)',
},
cc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'CC recipients (comma-separated)',
},
bcc: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'BCC recipients (comma-separated)',
},
attachments: {
type: 'file[]',
required: false,
visibility: 'user-only',
description: 'Files to attach to the email',
},
},
request: {
url: '/api/tools/outlook/send',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params: OutlookSendParams) => {
return {
accessToken: params.accessToken,
to: params.to,
subject: params.subject,
body: params.body,
contentType: params.contentType || 'text',
cc: params.cc || null,
bcc: params.bcc || null,
replyToMessageId: params.replyToMessageId || null,
attachments: params.attachments || null,
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Failed to send Outlook email')
}
return {
success: true,
output: {
message: data.output.message,
results: {
status: data.output.status,
timestamp: data.output.timestamp,
attachmentCount: data.output.attachmentCount || 0,
},
},
}
},
outputs: {
success: { type: 'boolean', description: 'Email send success status' },
status: { type: 'string', description: 'Delivery status of the email' },
timestamp: { type: 'string', description: 'Timestamp when email was sent' },
message: { type: 'string', description: 'Success or error message' },
},
}
+524
View File
@@ -0,0 +1,524 @@
import type { UserFile } from '@/executor/types'
import type { OutputProperty, ToolResponse } from '@/tools/types'
/**
* Output property definitions for Microsoft Graph Outlook API responses.
* @see https://learn.microsoft.com/en-us/graph/api/resources/message
*/
/**
* Output definition for email address objects.
* @see https://learn.microsoft.com/en-us/graph/api/resources/emailaddress
*/
export const OUTLOOK_EMAIL_ADDRESS_OUTPUT_PROPERTIES = {
name: { type: 'string', description: 'Display name of the person or entity', optional: true },
address: { type: 'string', description: 'Email address' },
} as const satisfies Record<string, OutputProperty>
/**
* Output definition for message body objects.
* @see https://learn.microsoft.com/en-us/graph/api/resources/itembody
*/
export const OUTLOOK_MESSAGE_BODY_OUTPUT_PROPERTIES = {
contentType: { type: 'string', description: 'Body content type (text or html)', optional: true },
content: { type: 'string', description: 'Body content', optional: true },
} as const satisfies Record<string, OutputProperty>
/**
* Output definition for cleaned message objects returned by our tools.
*/
export const OUTLOOK_MESSAGE_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'Unique message identifier' },
subject: { type: 'string', description: 'Email subject', optional: true },
bodyPreview: { type: 'string', description: 'Preview of the message body', optional: true },
body: {
type: 'object',
description: 'Message body',
optional: true,
properties: OUTLOOK_MESSAGE_BODY_OUTPUT_PROPERTIES,
},
sender: {
type: 'object',
description: 'Sender information',
optional: true,
properties: OUTLOOK_EMAIL_ADDRESS_OUTPUT_PROPERTIES,
},
from: {
type: 'object',
description: 'From address information',
optional: true,
properties: OUTLOOK_EMAIL_ADDRESS_OUTPUT_PROPERTIES,
},
toRecipients: {
type: 'array',
description: 'To recipients',
items: {
type: 'object',
properties: OUTLOOK_EMAIL_ADDRESS_OUTPUT_PROPERTIES,
},
},
ccRecipients: {
type: 'array',
description: 'CC recipients',
items: {
type: 'object',
properties: OUTLOOK_EMAIL_ADDRESS_OUTPUT_PROPERTIES,
},
},
receivedDateTime: {
type: 'string',
description: 'When the message was received (ISO 8601)',
optional: true,
},
sentDateTime: {
type: 'string',
description: 'When the message was sent (ISO 8601)',
optional: true,
},
hasAttachments: {
type: 'boolean',
description: 'Whether the message has attachments',
optional: true,
},
isRead: { type: 'boolean', description: 'Whether the message has been read', optional: true },
importance: {
type: 'string',
description: 'Message importance (low, normal, high)',
optional: true,
},
} as const satisfies Record<string, OutputProperty>
/**
* Complete message output definition
*/
export const OUTLOOK_MESSAGE_OUTPUT: OutputProperty = {
type: 'object',
description: 'Outlook email message',
properties: OUTLOOK_MESSAGE_OUTPUT_PROPERTIES,
}
/**
* Output definition for attachment objects.
* @see https://learn.microsoft.com/en-us/graph/api/resources/attachment
*/
export const OUTLOOK_ATTACHMENT_OUTPUT_PROPERTIES = {
name: { type: 'string', description: 'Attachment filename' },
contentType: { type: 'string', description: 'MIME type of the attachment' },
size: { type: 'number', description: 'Attachment size in bytes' },
} as const satisfies Record<string, OutputProperty>
export interface OutlookSendParams {
accessToken: string
to: string
subject: string
body: string
contentType?: 'text' | 'html'
replyToMessageId?: string
cc?: string
bcc?: string
attachments?: UserFile[]
}
export interface OutlookSendResponse extends ToolResponse {
output: {
message: string
results: any
}
}
export interface OutlookReadParams {
accessToken: string
folder: string
maxResults: number
messageId?: string
includeAttachments?: boolean
}
export interface OutlookReadResponse extends ToolResponse {
output: {
message: string
results: CleanedOutlookMessage[]
}
}
export interface OutlookDraftParams {
accessToken: string
to: string
cc?: string
bcc?: string
subject: string
body: string
contentType?: 'text' | 'html'
attachments?: UserFile[]
}
export interface OutlookDraftResponse extends ToolResponse {
output: {
message: string
results: any
}
}
// Outlook API response interfaces
interface OutlookEmailAddress {
name?: string
address: string
}
interface OutlookRecipient {
emailAddress: OutlookEmailAddress
}
interface OutlookMessageBody {
contentType?: string
content?: string
}
export interface OutlookMessage {
id: string
subject?: string
bodyPreview?: string
body?: OutlookMessageBody
sender?: OutlookRecipient
from?: OutlookRecipient
toRecipients?: OutlookRecipient[]
ccRecipients?: OutlookRecipient[]
bccRecipients?: OutlookRecipient[]
receivedDateTime?: string
sentDateTime?: string
hasAttachments?: boolean
isRead?: boolean
importance?: string
// Add other common fields
'@odata.etag'?: string
createdDateTime?: string
lastModifiedDateTime?: string
changeKey?: string
categories?: string[]
internetMessageId?: string
parentFolderId?: string
conversationId?: string
conversationIndex?: string
isDeliveryReceiptRequested?: boolean | null
isReadReceiptRequested?: boolean
isDraft?: boolean
webLink?: string
inferenceClassification?: string
replyTo?: OutlookRecipient[]
}
export interface OutlookMessagesResponse {
'@odata.context'?: string
'@odata.nextLink'?: string
value: OutlookMessage[]
}
// Outlook attachment interface (for tool responses)
export interface OutlookAttachment {
name: string
data: string
contentType: string
size: number
}
// Cleaned message interface for our response
export interface CleanedOutlookMessage {
id: string
subject?: string
bodyPreview?: string
body?: {
contentType?: string
content?: string
}
sender?: {
name?: string
address?: string
}
from?: {
name?: string
address?: string
}
toRecipients: Array<{
name?: string
address?: string
}>
ccRecipients: Array<{
name?: string
address?: string
}>
receivedDateTime?: string
sentDateTime?: string
hasAttachments?: boolean
attachments?: OutlookAttachment[] | any[]
isRead?: boolean
importance?: string
}
export type OutlookResponse = OutlookReadResponse | OutlookSendResponse | OutlookDraftResponse
export interface OutlookForwardParams {
accessToken: string
messageId: string
to: string
comment?: string
}
export interface OutlookForwardResponse extends ToolResponse {
output: {
message: string
results: any
}
}
export interface OutlookMoveParams {
accessToken: string
messageId: string
destinationId: string
}
export interface OutlookMoveResponse extends ToolResponse {
output: {
message: string
results: {
messageId: string
newFolderId: string
}
}
}
export interface OutlookMarkReadParams {
accessToken: string
messageId: string
}
export interface OutlookMarkReadResponse extends ToolResponse {
output: {
message: string
results: {
messageId: string
isRead: boolean
}
}
}
export interface OutlookDeleteParams {
accessToken: string
messageId: string
}
export interface OutlookDeleteResponse extends ToolResponse {
output: {
message: string
results: {
messageId: string
status: string
}
}
}
export interface OutlookCopyParams {
accessToken: string
messageId: string
destinationId: string
}
export interface OutlookCopyResponse extends ToolResponse {
output: {
message: string
results: {
originalMessageId: string
copiedMessageId: string
destinationFolderId: string
}
}
}
export type OutlookExtendedResponse =
| OutlookResponse
| OutlookForwardResponse
| OutlookMoveResponse
| OutlookMarkReadResponse
| OutlookDeleteResponse
| OutlookCopyResponse
/**
* Output definition for mail folder objects.
* @see https://learn.microsoft.com/en-us/graph/api/resources/mailfolder
*/
export const OUTLOOK_FOLDER_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'Unique folder identifier' },
displayName: { type: 'string', description: 'Display name of the folder', optional: true },
parentFolderId: {
type: 'string',
description: 'Identifier of the parent folder',
optional: true,
},
childFolderCount: {
type: 'number',
description: 'Number of immediate child folders',
optional: true,
},
unreadItemCount: {
type: 'number',
description: 'Number of unread items in the folder',
optional: true,
},
totalItemCount: {
type: 'number',
description: 'Total number of items in the folder',
optional: true,
},
isHidden: { type: 'boolean', description: 'Whether the folder is hidden', optional: true },
} as const satisfies Record<string, OutputProperty>
/**
* Output definition for attachment metadata returned by list/get attachment tools.
*/
export const OUTLOOK_ATTACHMENT_METADATA_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'Unique attachment identifier' },
name: { type: 'string', description: 'Attachment filename', optional: true },
contentType: { type: 'string', description: 'MIME type of the attachment', optional: true },
size: { type: 'number', description: 'Attachment size in bytes', optional: true },
isInline: {
type: 'boolean',
description: 'Whether the attachment is rendered inline in the message body',
optional: true,
},
attachmentType: {
type: 'string',
description: 'Microsoft Graph attachment type (e.g. #microsoft.graph.fileAttachment)',
optional: true,
},
lastModifiedDateTime: {
type: 'string',
description: 'When the attachment was last modified (ISO 8601)',
optional: true,
},
} as const satisfies Record<string, OutputProperty>
/** Cleaned mail folder returned by our tools. */
export interface CleanedOutlookFolder {
id: string
displayName?: string | null
parentFolderId?: string | null
childFolderCount?: number | null
unreadItemCount?: number | null
totalItemCount?: number | null
isHidden?: boolean | null
}
/** Cleaned attachment metadata returned by our tools. */
export interface CleanedOutlookAttachmentMetadata {
id: string
name?: string | null
contentType?: string | null
size?: number | null
isInline?: boolean | null
attachmentType?: string | null
lastModifiedDateTime?: string | null
}
export interface OutlookReplyParams {
accessToken: string
messageId: string
comment?: string
}
export interface OutlookReplyResponse extends ToolResponse {
output: {
message: string
results: {
status: string
timestamp: string
httpStatus?: number
requestId?: string
}
}
}
export interface OutlookListFoldersParams {
accessToken: string
maxResults?: number
includeHiddenFolders?: boolean
}
export interface OutlookListFoldersResponse extends ToolResponse {
output: {
message: string
results: CleanedOutlookFolder[]
}
}
export interface OutlookCreateFolderParams {
accessToken: string
displayName: string
isHidden?: boolean
}
export interface OutlookCreateFolderResponse extends ToolResponse {
output: {
message: string
results: CleanedOutlookFolder
}
}
export interface OutlookListAttachmentsParams {
accessToken: string
messageId: string
}
export interface OutlookListAttachmentsResponse extends ToolResponse {
output: {
message: string
results: CleanedOutlookAttachmentMetadata[]
}
}
export interface OutlookGetAttachmentParams {
accessToken: string
messageId: string
attachmentId: string
}
export interface OutlookGetAttachmentResponse extends ToolResponse {
output: {
message: string
results: CleanedOutlookAttachmentMetadata
attachments: OutlookAttachment[]
}
}
export interface OutlookSearchParams {
accessToken: string
query: string
maxResults?: number
}
export interface OutlookSearchResponse extends ToolResponse {
output: {
message: string
results: CleanedOutlookMessage[]
}
}
export interface OutlookUpdateMessageParams {
accessToken: string
messageId: string
categories?: string[]
flagStatus?: 'notFlagged' | 'flagged' | 'complete'
importance?: 'low' | 'normal' | 'high'
}
export interface OutlookUpdateMessageResponse extends ToolResponse {
output: {
message: string
results: {
messageId: string
subject?: string | null
categories: string[]
flagStatus?: string | null
importance?: string | null
isRead?: boolean | null
}
}
}
+162
View File
@@ -0,0 +1,162 @@
import type {
OutlookUpdateMessageParams,
OutlookUpdateMessageResponse,
} from '@/tools/outlook/types'
import type { ToolConfig } from '@/tools/types'
interface OutlookMessageUpdateApi {
id: string
subject?: string
categories?: string[]
flag?: { flagStatus?: string }
importance?: string
isRead?: boolean
}
/**
* Normalize message categories into a trimmed string array. Accepts an array, a
* JSON-array string, or a comma/newline-separated string (the `json`-typed param
* can arrive in any of these forms from block inputs or agent tool-calls).
*/
function normalizeCategories(value: unknown): string[] {
let items: unknown[] = []
if (Array.isArray(value)) {
items = value
} else if (typeof value === 'string') {
const trimmed = value.trim()
try {
const parsed = JSON.parse(trimmed)
items = Array.isArray(parsed) ? parsed : trimmed.split(/[,\n]/)
} catch {
items = trimmed.split(/[,\n]/)
}
}
return items.map((item) => String(item).trim()).filter(Boolean)
}
export const outlookUpdateMessageTool: ToolConfig<
OutlookUpdateMessageParams,
OutlookUpdateMessageResponse
> = {
id: 'outlook_update_message',
name: 'Outlook Update Message',
description: 'Set the categories, follow-up flag, and importance on an Outlook message',
version: '1.0.0',
oauth: {
required: true,
provider: 'outlook',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token for Outlook',
},
messageId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the message to update',
},
categories: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'Array of category names to assign to the message (replaces existing categories; leave empty to keep them unchanged)',
},
flagStatus: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Follow-up flag status: notFlagged, flagged, or complete',
},
importance: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Message importance: low, normal, or high',
},
},
request: {
url: (params) => `https://graph.microsoft.com/v1.0/me/messages/${params.messageId.trim()}`,
method: 'PATCH',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Access token is required')
}
return {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
},
body: (params) => {
const body: Record<string, unknown> = {}
const normalizedCategories = normalizeCategories(params.categories)
if (normalizedCategories.length > 0) {
body.categories = normalizedCategories
}
if (params.flagStatus) {
body.flag = { flagStatus: params.flagStatus }
}
if (params.importance) {
body.importance = params.importance
}
if (Object.keys(body).length === 0) {
throw new Error('Provide at least one of categories, flagStatus, or importance to update')
}
return body
},
},
transformResponse: async (response: Response) => {
const message: OutlookMessageUpdateApi = await response.json()
return {
success: true,
output: {
message: 'Message updated successfully',
results: {
messageId: message.id,
subject: message.subject ?? null,
categories: message.categories ?? [],
flagStatus: message.flag?.flagStatus ?? null,
importance: message.importance ?? null,
isRead: message.isRead ?? null,
},
},
}
},
outputs: {
message: { type: 'string', description: 'Success or error message' },
results: {
type: 'object',
description: 'Updated message details',
properties: {
messageId: { type: 'string', description: 'ID of the updated message' },
subject: { type: 'string', description: 'Subject of the message', optional: true },
categories: {
type: 'array',
description: 'Categories assigned to the message',
items: { type: 'string' },
},
flagStatus: {
type: 'string',
description: 'Follow-up flag status of the message',
optional: true,
},
importance: { type: 'string', description: 'Importance of the message', optional: true },
isRead: { type: 'boolean', description: 'Whether the message is read', optional: true },
},
},
},
}