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
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:
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user