chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
countGuests,
|
||||
type LumaAddGuestsParams,
|
||||
type LumaAddGuestsResponse,
|
||||
} from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const addGuestsTool: ToolConfig<LumaAddGuestsParams, LumaAddGuestsResponse> = {
|
||||
id: 'luma_add_guests',
|
||||
name: 'Luma Add Guests',
|
||||
description:
|
||||
'Add guests to a Luma event by email. Guests are added with Going (approved) status and receive one ticket of the default ticket type.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID (starts with evt-)',
|
||||
},
|
||||
guests: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of guest objects. Each guest requires an "email" field and optionally "name", "first_name", "last_name". Example: [{"email": "user@example.com", "name": "John Doe"}]',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://public-api.luma.com/v1/event/add-guests',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let guestsArray: unknown[]
|
||||
try {
|
||||
guestsArray = typeof params.guests === 'string' ? JSON.parse(params.guests) : params.guests
|
||||
} catch {
|
||||
guestsArray = [{ email: params.guests }]
|
||||
}
|
||||
return {
|
||||
event_id: params.eventId.trim(),
|
||||
guests: guestsArray,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to add guests')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
added: countGuests(params?.guests ?? ''),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
added: {
|
||||
type: 'number',
|
||||
description: 'Number of guests submitted to the event (added with Going/approved status)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { LumaCancelEventParams, LumaCancelEventResponse } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const cancelEventTool: ToolConfig<LumaCancelEventParams, LumaCancelEventResponse> = {
|
||||
id: 'luma_cancel_event',
|
||||
name: 'Luma Cancel Event',
|
||||
description:
|
||||
'Cancel a Luma event. This is irreversible and notifies all registered guests. Requires a cancellation token obtained from the Request Event Cancellation endpoint.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID to cancel (starts with evt-)',
|
||||
},
|
||||
cancellationToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Cancellation token from the Request Event Cancellation endpoint (POST /v1/event/cancel/request)',
|
||||
},
|
||||
shouldRefund: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to refund paid guests. Required if the event has paid registrations.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://public-api.luma.com/v1/event/cancel',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
event_id: params.eventId.trim(),
|
||||
cancellation_token: params.cancellationToken.trim(),
|
||||
}
|
||||
if (params.shouldRefund !== undefined) body.should_refund = params.shouldRefund
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to cancel event')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
cancelled: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
cancelled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the event was successfully cancelled',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import type { LumaCreateEventParams, LumaCreateEventResponse } from '@/tools/luma/types'
|
||||
import {
|
||||
LUMA_EVENT_OUTPUT_PROPERTIES,
|
||||
LUMA_HOST_OUTPUT_PROPERTIES,
|
||||
lumaEventStub,
|
||||
} from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createEventTool: ToolConfig<LumaCreateEventParams, LumaCreateEventResponse> = {
|
||||
id: 'luma_create_event',
|
||||
name: 'Luma Create Event',
|
||||
description:
|
||||
'Create a new event on Luma with a name, start time, timezone, and optional details like description, location, and visibility.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event name/title',
|
||||
},
|
||||
startAt: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event start time in ISO 8601 format (e.g., 2025-03-15T18:00:00Z)',
|
||||
},
|
||||
timezone: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IANA timezone (e.g., America/New_York, Europe/London)',
|
||||
},
|
||||
endAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event end time in ISO 8601 format (e.g., 2025-03-15T20:00:00Z)',
|
||||
},
|
||||
durationInterval: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Event duration as ISO 8601 interval (e.g., PT2H for 2 hours, PT30M for 30 minutes). Used if endAt is not provided.',
|
||||
},
|
||||
descriptionMd: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event description in Markdown format',
|
||||
},
|
||||
meetingUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Virtual meeting URL for online events (e.g., Zoom, Google Meet link)',
|
||||
},
|
||||
visibility: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event visibility: public, members-only, or private (defaults to public)',
|
||||
},
|
||||
coverUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cover image URL (must be a Luma CDN URL from images.lumacdn.com)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://public-api.luma.com/v1/event/create',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
start_at: params.startAt,
|
||||
timezone: params.timezone,
|
||||
}
|
||||
if (params.endAt) body.end_at = params.endAt
|
||||
if (params.durationInterval) body.duration_interval = params.durationInterval
|
||||
if (params.descriptionMd) body.description_md = params.descriptionMd
|
||||
if (params.meetingUrl) body.meeting_url = params.meetingUrl
|
||||
if (params.visibility) body.visibility = params.visibility
|
||||
if (params.coverUrl) body.cover_url = params.coverUrl
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to create event')
|
||||
}
|
||||
|
||||
const eventId: string | null = data.id ?? data.api_id ?? null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
event: lumaEventStub(eventId),
|
||||
hosts: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
postProcess: async (result, params, executeTool) => {
|
||||
const eventId = result.success ? result.output.event.id : null
|
||||
if (!eventId) return result
|
||||
|
||||
const full = await executeTool('luma_get_event', {
|
||||
apiKey: params.apiKey,
|
||||
eventId,
|
||||
})
|
||||
if (full.success && full.output?.event) {
|
||||
return full as LumaCreateEventResponse
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
event: {
|
||||
type: 'object',
|
||||
description: 'Created event details',
|
||||
properties: LUMA_EVENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
hosts: {
|
||||
type: 'array',
|
||||
description: 'Event hosts',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LUMA_HOST_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { LumaGetEventParams, LumaGetEventResponse } from '@/tools/luma/types'
|
||||
import { LUMA_EVENT_OUTPUT_PROPERTIES, LUMA_HOST_OUTPUT_PROPERTIES } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getEventTool: ToolConfig<LumaGetEventParams, LumaGetEventResponse> = {
|
||||
id: 'luma_get_event',
|
||||
name: 'Luma Get Event',
|
||||
description:
|
||||
'Retrieve details of a Luma event including name, time, location, hosts, and visibility settings.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID (starts with evt-)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://public-api.luma.com/v1/event/get')
|
||||
url.searchParams.set('id', params.eventId.trim())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to get event')
|
||||
}
|
||||
|
||||
const event = data.event
|
||||
const hosts = (data.hosts ?? []).map((h: Record<string, unknown>) => ({
|
||||
id: (h.id as string) ?? null,
|
||||
name: (h.name as string) ?? null,
|
||||
firstName: (h.first_name as string) ?? null,
|
||||
lastName: (h.last_name as string) ?? null,
|
||||
email: (h.email as string) ?? null,
|
||||
avatarUrl: (h.avatar_url as string) ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
event: {
|
||||
id: event.id ?? null,
|
||||
name: event.name ?? null,
|
||||
startAt: event.start_at ?? null,
|
||||
endAt: event.end_at ?? null,
|
||||
timezone: event.timezone ?? null,
|
||||
durationInterval: event.duration_interval ?? null,
|
||||
createdAt: event.created_at ?? null,
|
||||
description: event.description ?? null,
|
||||
descriptionMd: event.description_md ?? null,
|
||||
coverUrl: event.cover_url ?? null,
|
||||
url: event.url ?? null,
|
||||
visibility: event.visibility ?? null,
|
||||
meetingUrl: event.meeting_url ?? null,
|
||||
geoAddressJson: event.geo_address_json ?? null,
|
||||
geoLatitude: event.geo_latitude ?? null,
|
||||
geoLongitude: event.geo_longitude ?? null,
|
||||
calendarId: event.calendar_id ?? null,
|
||||
},
|
||||
hosts,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
event: {
|
||||
type: 'object',
|
||||
description: 'Event details',
|
||||
properties: LUMA_EVENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
hosts: {
|
||||
type: 'array',
|
||||
description: 'Event hosts',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LUMA_HOST_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import type { LumaGetGuestParams, LumaGetGuestResponse } from '@/tools/luma/types'
|
||||
import { LUMA_GUEST_OUTPUT_PROPERTIES, resolveGuestCheckedInAt } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getGuestTool: ToolConfig<LumaGetGuestParams, LumaGetGuestResponse> = {
|
||||
id: 'luma_get_guest',
|
||||
name: 'Luma Get Guest',
|
||||
description:
|
||||
"Retrieve a single guest's details on a Luma event, including approval status, registration timestamps, and contact info.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID the guest belongs to (starts with evt-)',
|
||||
},
|
||||
guestIdentifier: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
"Guest ID (gst-...), guest key (g-...), ticket key, or the guest's email address",
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://public-api.luma.com/v1/events/guests/get')
|
||||
url.searchParams.set('event_id', params.eventId.trim())
|
||||
url.searchParams.set('id', params.guestIdentifier.trim())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to get guest')
|
||||
}
|
||||
|
||||
const guest = (data.guest as Record<string, unknown>) ?? data
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
guest: {
|
||||
id: (guest.id as string) ?? null,
|
||||
email: (guest.user_email as string) ?? null,
|
||||
name: (guest.user_name as string) ?? null,
|
||||
firstName: (guest.user_first_name as string) ?? null,
|
||||
lastName: (guest.user_last_name as string) ?? null,
|
||||
approvalStatus: (guest.approval_status as string) ?? null,
|
||||
registeredAt: (guest.registered_at as string) ?? null,
|
||||
invitedAt: (guest.invited_at as string) ?? null,
|
||||
joinedAt: (guest.joined_at as string) ?? null,
|
||||
checkedInAt: resolveGuestCheckedInAt(guest),
|
||||
phoneNumber: (guest.phone_number as string) ?? null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
guest: {
|
||||
type: 'object',
|
||||
description: 'Guest details',
|
||||
properties: LUMA_GUEST_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { LumaGetGuestsParams, LumaGetGuestsResponse } from '@/tools/luma/types'
|
||||
import { LUMA_GUEST_OUTPUT_PROPERTIES, resolveGuestCheckedInAt } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getGuestsTool: ToolConfig<LumaGetGuestsParams, LumaGetGuestsResponse> = {
|
||||
id: 'luma_get_guests',
|
||||
name: 'Luma Get Guests',
|
||||
description:
|
||||
'Retrieve the guest list for a Luma event with optional filtering by approval status, sorting, and pagination.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID (starts with evt-)',
|
||||
},
|
||||
approvalStatus: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by approval status: approved, session, pending_approval, invited, declined, or waitlist',
|
||||
},
|
||||
paginationLimit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of guests to return per page',
|
||||
},
|
||||
paginationCursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Pagination cursor from a previous response (next_cursor) to fetch the next page of results',
|
||||
},
|
||||
sortColumn: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Column to sort by: name, email, created_at, registered_at, or checked_in_at',
|
||||
},
|
||||
sortDirection: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort direction: asc, desc, asc nulls last, or desc nulls last',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://public-api.luma.com/v1/event/get-guests')
|
||||
url.searchParams.set('event_id', params.eventId.trim())
|
||||
if (params.approvalStatus) url.searchParams.set('approval_status', params.approvalStatus)
|
||||
if (params.paginationLimit)
|
||||
url.searchParams.set('pagination_limit', String(params.paginationLimit))
|
||||
if (params.paginationCursor)
|
||||
url.searchParams.set('pagination_cursor', params.paginationCursor)
|
||||
if (params.sortColumn) url.searchParams.set('sort_column', params.sortColumn)
|
||||
if (params.sortDirection) url.searchParams.set('sort_direction', params.sortDirection)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to get guests')
|
||||
}
|
||||
|
||||
const guests = (data.entries ?? []).map((entry: Record<string, unknown>) => {
|
||||
const guest = (entry.guest as Record<string, unknown>) ?? entry
|
||||
return {
|
||||
id: (guest.id as string) ?? null,
|
||||
email: (guest.user_email as string) ?? null,
|
||||
name: (guest.user_name as string) ?? null,
|
||||
firstName: (guest.user_first_name as string) ?? null,
|
||||
lastName: (guest.user_last_name as string) ?? null,
|
||||
approvalStatus: (guest.approval_status as string) ?? null,
|
||||
registeredAt: (guest.registered_at as string) ?? null,
|
||||
invitedAt: (guest.invited_at as string) ?? null,
|
||||
joinedAt: (guest.joined_at as string) ?? null,
|
||||
checkedInAt: resolveGuestCheckedInAt(guest),
|
||||
phoneNumber: (guest.phone_number as string) ?? null,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
guests,
|
||||
hasMore: data.has_more ?? false,
|
||||
nextCursor: data.next_cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
guests: {
|
||||
type: 'array',
|
||||
description: 'List of event guests',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LUMA_GUEST_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
hasMore: {
|
||||
type: 'boolean',
|
||||
description: 'Whether more results are available for pagination',
|
||||
},
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor to pass as paginationCursor to fetch the next page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { addGuestsTool } from '@/tools/luma/add_guests'
|
||||
import { cancelEventTool } from '@/tools/luma/cancel_event'
|
||||
import { createEventTool } from '@/tools/luma/create_event'
|
||||
import { getEventTool } from '@/tools/luma/get_event'
|
||||
import { getGuestTool } from '@/tools/luma/get_guest'
|
||||
import { getGuestsTool } from '@/tools/luma/get_guests'
|
||||
import { listEventsTool } from '@/tools/luma/list_events'
|
||||
import { lookupEventTool } from '@/tools/luma/lookup_event'
|
||||
import { sendInvitesTool } from '@/tools/luma/send_invites'
|
||||
import { updateEventTool } from '@/tools/luma/update_event'
|
||||
import { updateGuestStatusTool } from '@/tools/luma/update_guest_status'
|
||||
|
||||
export * from './types'
|
||||
|
||||
export const lumaAddGuestsTool = addGuestsTool
|
||||
export const lumaCancelEventTool = cancelEventTool
|
||||
export const lumaCreateEventTool = createEventTool
|
||||
export const lumaGetEventTool = getEventTool
|
||||
export const lumaGetGuestTool = getGuestTool
|
||||
export const lumaGetGuestsTool = getGuestsTool
|
||||
export const lumaListEventsTool = listEventsTool
|
||||
export const lumaLookupEventTool = lookupEventTool
|
||||
export const lumaSendInvitesTool = sendInvitesTool
|
||||
export const lumaUpdateEventTool = updateEventTool
|
||||
export const lumaUpdateGuestStatusTool = updateGuestStatusTool
|
||||
@@ -0,0 +1,137 @@
|
||||
import type { LumaListEventsParams, LumaListEventsResponse } from '@/tools/luma/types'
|
||||
import { LUMA_EVENT_OUTPUT_PROPERTIES } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listEventsTool: ToolConfig<LumaListEventsParams, LumaListEventsResponse> = {
|
||||
id: 'luma_list_events',
|
||||
name: 'Luma List Events',
|
||||
description:
|
||||
'List events from your Luma calendar with optional date range filtering, sorting, and pagination.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
after: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return events after this ISO 8601 datetime (e.g., 2025-01-01T00:00:00Z)',
|
||||
},
|
||||
before: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return events before this ISO 8601 datetime (e.g., 2025-12-31T23:59:59Z)',
|
||||
},
|
||||
paginationLimit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of events to return per page',
|
||||
},
|
||||
paginationCursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Pagination cursor from a previous response (next_cursor) to fetch the next page of results',
|
||||
},
|
||||
sortColumn: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Column to sort by (only start_at is supported)',
|
||||
},
|
||||
sortDirection: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort direction: asc, desc, asc nulls last, or desc nulls last',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://public-api.luma.com/v1/calendar/list-events')
|
||||
if (params.after) url.searchParams.set('after', params.after)
|
||||
if (params.before) url.searchParams.set('before', params.before)
|
||||
if (params.paginationLimit)
|
||||
url.searchParams.set('pagination_limit', String(params.paginationLimit))
|
||||
if (params.paginationCursor)
|
||||
url.searchParams.set('pagination_cursor', params.paginationCursor)
|
||||
if (params.sortColumn) url.searchParams.set('sort_column', params.sortColumn)
|
||||
if (params.sortDirection) url.searchParams.set('sort_direction', params.sortDirection)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to list events')
|
||||
}
|
||||
|
||||
const events = (data.entries ?? []).map((entry: Record<string, unknown>) => {
|
||||
const event = (entry.event as Record<string, unknown>) ?? entry
|
||||
return {
|
||||
id: (event.id as string) ?? null,
|
||||
name: (event.name as string) ?? null,
|
||||
startAt: (event.start_at as string) ?? null,
|
||||
endAt: (event.end_at as string) ?? null,
|
||||
timezone: (event.timezone as string) ?? null,
|
||||
durationInterval: (event.duration_interval as string) ?? null,
|
||||
createdAt: (event.created_at as string) ?? null,
|
||||
description: (event.description as string) ?? null,
|
||||
descriptionMd: (event.description_md as string) ?? null,
|
||||
coverUrl: (event.cover_url as string) ?? null,
|
||||
url: (event.url as string) ?? null,
|
||||
visibility: (event.visibility as string) ?? null,
|
||||
meetingUrl: (event.meeting_url as string) ?? null,
|
||||
geoAddressJson: (event.geo_address_json as Record<string, unknown>) ?? null,
|
||||
geoLatitude: (event.geo_latitude as string) ?? null,
|
||||
geoLongitude: (event.geo_longitude as string) ?? null,
|
||||
calendarId: (event.calendar_id as string) ?? null,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
events,
|
||||
hasMore: data.has_more ?? false,
|
||||
nextCursor: data.next_cursor ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
events: {
|
||||
type: 'array',
|
||||
description: 'List of calendar events',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LUMA_EVENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
hasMore: {
|
||||
type: 'boolean',
|
||||
description: 'Whether more results are available for pagination',
|
||||
},
|
||||
nextCursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor to pass as paginationCursor to fetch the next page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { LumaLookupEventParams, LumaLookupEventResponse } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const lookupEventTool: ToolConfig<LumaLookupEventParams, LumaLookupEventResponse> = {
|
||||
id: 'luma_lookup_event',
|
||||
name: 'Luma Lookup Event',
|
||||
description:
|
||||
'Look up an event by its public URL or event ID to resolve its canonical ID, API ID, and approval status.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public event URL on lu.ma (provide this or an event ID)',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID to look up (starts with evt-). Provide this or a URL.',
|
||||
},
|
||||
platform: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event platform to look up: luma or external (defaults to luma)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://public-api.luma.com/v1/calendar/lookup-event')
|
||||
if (params.url) url.searchParams.set('url', params.url.trim())
|
||||
if (params.eventId) url.searchParams.set('event_id', params.eventId.trim())
|
||||
if (params.platform) url.searchParams.set('platform', params.platform)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to look up event')
|
||||
}
|
||||
|
||||
const event = data.event as Record<string, unknown> | null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
found: Boolean(event),
|
||||
eventId: (event?.id as string) ?? null,
|
||||
apiId: (event?.api_id as string) ?? null,
|
||||
status: (event?.status as string) ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
found: {
|
||||
type: 'boolean',
|
||||
description: 'Whether a matching event was found',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
description: 'Resolved event ID',
|
||||
optional: true,
|
||||
},
|
||||
apiId: {
|
||||
type: 'string',
|
||||
description: 'Resolved event API ID (deprecated identifier)',
|
||||
optional: true,
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Event approval status (approved, pending, rejected)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
countGuests,
|
||||
type LumaSendInvitesParams,
|
||||
type LumaSendInvitesResponse,
|
||||
} from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const sendInvitesTool: ToolConfig<LumaSendInvitesParams, LumaSendInvitesResponse> = {
|
||||
id: 'luma_send_invites',
|
||||
name: 'Luma Send Invites',
|
||||
description:
|
||||
'Send email invitations to guests for a Luma event. Unlike Add Guests (which registers guests directly), this emails an invite that recipients can accept.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID to invite guests to (starts with evt-)',
|
||||
},
|
||||
guests: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON array of guest objects. Each guest requires an "email" field and optionally "name". Example: [{"email": "user@example.com", "name": "John Doe"}]',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional custom message included in the invite email (max 200 characters)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://public-api.luma.com/v1/event/send-invites',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
let guestsArray: unknown[]
|
||||
try {
|
||||
guestsArray = typeof params.guests === 'string' ? JSON.parse(params.guests) : params.guests
|
||||
} catch {
|
||||
guestsArray = [{ email: params.guests }]
|
||||
}
|
||||
const body: Record<string, unknown> = {
|
||||
event_id: params.eventId.trim(),
|
||||
guests: guestsArray,
|
||||
}
|
||||
if (params.message) body.message = params.message
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to send invites')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
invited: countGuests(params?.guests ?? ''),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
invited: {
|
||||
type: 'number',
|
||||
description: 'Number of guests invited to the event',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
export interface LumaGetEventParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
}
|
||||
|
||||
export interface LumaCreateEventParams {
|
||||
apiKey: string
|
||||
name: string
|
||||
startAt: string
|
||||
timezone: string
|
||||
durationInterval?: string
|
||||
endAt?: string
|
||||
descriptionMd?: string
|
||||
meetingUrl?: string
|
||||
visibility?: string
|
||||
coverUrl?: string
|
||||
}
|
||||
|
||||
export interface LumaListEventsParams {
|
||||
apiKey: string
|
||||
after?: string
|
||||
before?: string
|
||||
paginationLimit?: number
|
||||
paginationCursor?: string
|
||||
sortColumn?: string
|
||||
sortDirection?: string
|
||||
}
|
||||
|
||||
export interface LumaGetGuestsParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
approvalStatus?: string
|
||||
paginationLimit?: number
|
||||
paginationCursor?: string
|
||||
sortColumn?: string
|
||||
sortDirection?: string
|
||||
}
|
||||
|
||||
export interface LumaUpdateEventParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
name?: string
|
||||
startAt?: string
|
||||
timezone?: string
|
||||
endAt?: string
|
||||
durationInterval?: string
|
||||
descriptionMd?: string
|
||||
meetingUrl?: string
|
||||
visibility?: string
|
||||
coverUrl?: string
|
||||
}
|
||||
|
||||
export interface LumaAddGuestsParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
guests: string
|
||||
}
|
||||
|
||||
export interface LumaSendInvitesParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
guests: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface LumaUpdateGuestStatusParams {
|
||||
apiKey: string
|
||||
eventId?: string
|
||||
guestIdentifier: string
|
||||
status: string
|
||||
shouldRefund?: boolean
|
||||
sendEmail?: boolean
|
||||
}
|
||||
|
||||
export interface LumaGetGuestParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
guestIdentifier: string
|
||||
}
|
||||
|
||||
export interface LumaCancelEventParams {
|
||||
apiKey: string
|
||||
eventId: string
|
||||
cancellationToken: string
|
||||
shouldRefund?: boolean
|
||||
}
|
||||
|
||||
export interface LumaLookupEventParams {
|
||||
apiKey: string
|
||||
url?: string
|
||||
eventId?: string
|
||||
platform?: string
|
||||
}
|
||||
|
||||
interface LumaHostEntry {
|
||||
id: string | null
|
||||
name: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
email: string | null
|
||||
avatarUrl: string | null
|
||||
}
|
||||
|
||||
interface LumaEventEntry {
|
||||
id: string
|
||||
name: string
|
||||
startAt: string | null
|
||||
endAt: string | null
|
||||
timezone: string | null
|
||||
durationInterval: string | null
|
||||
createdAt: string | null
|
||||
description: string | null
|
||||
descriptionMd: string | null
|
||||
coverUrl: string | null
|
||||
url: string | null
|
||||
visibility: string | null
|
||||
meetingUrl: string | null
|
||||
geoAddressJson: Record<string, unknown> | null
|
||||
geoLatitude: string | null
|
||||
geoLongitude: string | null
|
||||
calendarId: string | null
|
||||
}
|
||||
|
||||
interface LumaGuestEntry {
|
||||
id: string
|
||||
email: string | null
|
||||
name: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
approvalStatus: string | null
|
||||
registeredAt: string | null
|
||||
invitedAt: string | null
|
||||
joinedAt: string | null
|
||||
checkedInAt: string | null
|
||||
phoneNumber: string | null
|
||||
}
|
||||
|
||||
export interface LumaGetEventResponse extends ToolResponse {
|
||||
output: {
|
||||
event: LumaEventEntry
|
||||
hosts: LumaHostEntry[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaCreateEventResponse extends ToolResponse {
|
||||
output: {
|
||||
event: LumaEventEntry
|
||||
hosts: LumaHostEntry[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaUpdateEventResponse extends ToolResponse {
|
||||
output: {
|
||||
event: LumaEventEntry
|
||||
hosts: LumaHostEntry[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaListEventsResponse extends ToolResponse {
|
||||
output: {
|
||||
events: LumaEventEntry[]
|
||||
hasMore: boolean
|
||||
nextCursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaGetGuestsResponse extends ToolResponse {
|
||||
output: {
|
||||
guests: LumaGuestEntry[]
|
||||
hasMore: boolean
|
||||
nextCursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaAddGuestsResponse extends ToolResponse {
|
||||
output: {
|
||||
added: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaSendInvitesResponse extends ToolResponse {
|
||||
output: {
|
||||
invited: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaUpdateGuestStatusResponse extends ToolResponse {
|
||||
output: {
|
||||
status: string
|
||||
guest: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaGetGuestResponse extends ToolResponse {
|
||||
output: {
|
||||
guest: LumaGuestEntry
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaCancelEventResponse extends ToolResponse {
|
||||
output: {
|
||||
cancelled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface LumaLookupEventResponse extends ToolResponse {
|
||||
output: {
|
||||
found: boolean
|
||||
eventId: string | null
|
||||
apiId: string | null
|
||||
status: string | null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts guests in a guests param value. Accepts a JSON array string (the
|
||||
* standard input shape) or a bare email string. Used by Add Guests and Send
|
||||
* Invites, whose endpoints return an empty body — the submitted count is the
|
||||
* only meaningful signal to report back.
|
||||
*/
|
||||
export function countGuests(guests: string): number {
|
||||
try {
|
||||
const parsed = JSON.parse(guests)
|
||||
if (Array.isArray(parsed)) return parsed.length
|
||||
return parsed ? 1 : 0
|
||||
} catch {
|
||||
return guests.trim() ? 1 : 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a guest's check-in time. Per the Luma API docs the canonical
|
||||
* check-in lives per ticket on `event_tickets[].checked_in_at`; the top-level
|
||||
* `checked_in_at` is deprecated. Returns the first checked-in ticket's
|
||||
* timestamp, falling back to the deprecated top-level field.
|
||||
*/
|
||||
export function resolveGuestCheckedInAt(guest: Record<string, unknown>): string | null {
|
||||
const tickets = guest.event_tickets
|
||||
if (Array.isArray(tickets)) {
|
||||
for (const ticket of tickets) {
|
||||
const checkedInAt = (ticket as Record<string, unknown>)?.checked_in_at
|
||||
if (checkedInAt) return checkedInAt as string
|
||||
}
|
||||
}
|
||||
return (guest.checked_in_at as string) ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an event stub with only the id populated. Used by create/update,
|
||||
* whose API responses do not return the full event object — the full record
|
||||
* is fetched in a follow-up Get Event call via postProcess.
|
||||
*/
|
||||
export function lumaEventStub(id: string | null): LumaEventEntry {
|
||||
return {
|
||||
id: id ?? '',
|
||||
name: '',
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
timezone: null,
|
||||
durationInterval: null,
|
||||
createdAt: null,
|
||||
description: null,
|
||||
descriptionMd: null,
|
||||
coverUrl: null,
|
||||
url: null,
|
||||
visibility: null,
|
||||
meetingUrl: null,
|
||||
geoAddressJson: null,
|
||||
geoLatitude: null,
|
||||
geoLongitude: null,
|
||||
calendarId: null,
|
||||
}
|
||||
}
|
||||
|
||||
export const LUMA_HOST_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string' as const, description: 'Host ID' },
|
||||
name: { type: 'string' as const, description: 'Host display name' },
|
||||
firstName: { type: 'string' as const, description: 'Host first name' },
|
||||
lastName: { type: 'string' as const, description: 'Host last name' },
|
||||
email: { type: 'string' as const, description: 'Host email address' },
|
||||
avatarUrl: { type: 'string' as const, description: 'Host avatar image URL' },
|
||||
}
|
||||
|
||||
export const LUMA_EVENT_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string' as const, description: 'Event ID' },
|
||||
name: { type: 'string' as const, description: 'Event name' },
|
||||
startAt: { type: 'string' as const, description: 'Event start time (ISO 8601)' },
|
||||
endAt: { type: 'string' as const, description: 'Event end time (ISO 8601)' },
|
||||
timezone: { type: 'string' as const, description: 'Event timezone (IANA)' },
|
||||
durationInterval: {
|
||||
type: 'string' as const,
|
||||
description: 'Event duration (ISO 8601 interval, e.g. PT2H)',
|
||||
},
|
||||
createdAt: { type: 'string' as const, description: 'Event creation timestamp (ISO 8601)' },
|
||||
description: { type: 'string' as const, description: 'Event description (plain text)' },
|
||||
descriptionMd: { type: 'string' as const, description: 'Event description (Markdown)' },
|
||||
coverUrl: { type: 'string' as const, description: 'Event cover image URL' },
|
||||
url: { type: 'string' as const, description: 'Event page URL on lu.ma' },
|
||||
visibility: {
|
||||
type: 'string' as const,
|
||||
description: 'Event visibility (public, members-only, private)',
|
||||
},
|
||||
meetingUrl: { type: 'string' as const, description: 'Virtual meeting URL' },
|
||||
geoAddressJson: { type: 'json' as const, description: 'Structured location/address data' },
|
||||
geoLatitude: { type: 'string' as const, description: 'Venue latitude coordinate' },
|
||||
geoLongitude: { type: 'string' as const, description: 'Venue longitude coordinate' },
|
||||
calendarId: { type: 'string' as const, description: 'Associated calendar ID' },
|
||||
}
|
||||
|
||||
export const LUMA_GUEST_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string' as const, description: 'Guest ID' },
|
||||
email: { type: 'string' as const, description: 'Guest email address' },
|
||||
name: { type: 'string' as const, description: 'Guest full name' },
|
||||
firstName: { type: 'string' as const, description: 'Guest first name' },
|
||||
lastName: { type: 'string' as const, description: 'Guest last name' },
|
||||
approvalStatus: {
|
||||
type: 'string' as const,
|
||||
description:
|
||||
'Guest approval status (approved, session, pending_approval, invited, declined, waitlist)',
|
||||
},
|
||||
registeredAt: { type: 'string' as const, description: 'Registration timestamp (ISO 8601)' },
|
||||
invitedAt: { type: 'string' as const, description: 'Invitation timestamp (ISO 8601)' },
|
||||
joinedAt: { type: 'string' as const, description: 'Join timestamp (ISO 8601)' },
|
||||
checkedInAt: {
|
||||
type: 'string' as const,
|
||||
description: 'Check-in timestamp from the first checked-in ticket (ISO 8601)',
|
||||
},
|
||||
phoneNumber: { type: 'string' as const, description: 'Guest phone number' },
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import type { LumaUpdateEventParams, LumaUpdateEventResponse } from '@/tools/luma/types'
|
||||
import {
|
||||
LUMA_EVENT_OUTPUT_PROPERTIES,
|
||||
LUMA_HOST_OUTPUT_PROPERTIES,
|
||||
lumaEventStub,
|
||||
} from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateEventTool: ToolConfig<LumaUpdateEventParams, LumaUpdateEventResponse> = {
|
||||
id: 'luma_update_event',
|
||||
name: 'Luma Update Event',
|
||||
description:
|
||||
'Update an existing Luma event. Only the fields you provide will be changed; all other fields remain unchanged.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID to update (starts with evt-)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New event name/title',
|
||||
},
|
||||
startAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New start time in ISO 8601 format (e.g., 2025-03-15T18:00:00Z)',
|
||||
},
|
||||
timezone: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New IANA timezone (e.g., America/New_York, Europe/London)',
|
||||
},
|
||||
endAt: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New end time in ISO 8601 format (e.g., 2025-03-15T20:00:00Z)',
|
||||
},
|
||||
durationInterval: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'New duration as ISO 8601 interval (e.g., PT2H for 2 hours). Used if endAt is not provided.',
|
||||
},
|
||||
descriptionMd: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New event description in Markdown format',
|
||||
},
|
||||
meetingUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New virtual meeting URL (e.g., Zoom, Google Meet link)',
|
||||
},
|
||||
visibility: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New visibility: public, members-only, or private',
|
||||
},
|
||||
coverUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New cover image URL (must be a Luma CDN URL from images.lumacdn.com)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://public-api.luma.com/v1/event/update',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
event_id: params.eventId.trim(),
|
||||
}
|
||||
if (params.name) body.name = params.name
|
||||
if (params.startAt) body.start_at = params.startAt
|
||||
if (params.timezone) body.timezone = params.timezone
|
||||
if (params.endAt) body.end_at = params.endAt
|
||||
if (params.durationInterval) body.duration_interval = params.durationInterval
|
||||
if (params.descriptionMd) body.description_md = params.descriptionMd
|
||||
if (params.meetingUrl) body.meeting_url = params.meetingUrl
|
||||
if (params.visibility) body.visibility = params.visibility
|
||||
if (params.coverUrl) body.cover_url = params.coverUrl
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to update event')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
event: lumaEventStub(params?.eventId?.trim() ?? null),
|
||||
hosts: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
postProcess: async (result, params, executeTool) => {
|
||||
const eventId = result.success ? result.output.event.id : null
|
||||
if (!eventId) return result
|
||||
|
||||
const full = await executeTool('luma_get_event', {
|
||||
apiKey: params.apiKey,
|
||||
eventId,
|
||||
})
|
||||
if (full.success && full.output?.event) {
|
||||
return full as LumaUpdateEventResponse
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
event: {
|
||||
type: 'object',
|
||||
description: 'Updated event details',
|
||||
properties: LUMA_EVENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
hosts: {
|
||||
type: 'array',
|
||||
description: 'Event hosts',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: LUMA_HOST_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { LumaUpdateGuestStatusParams, LumaUpdateGuestStatusResponse } from '@/tools/luma/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateGuestStatusTool: ToolConfig<
|
||||
LumaUpdateGuestStatusParams,
|
||||
LumaUpdateGuestStatusResponse
|
||||
> = {
|
||||
id: 'luma_update_guest_status',
|
||||
name: 'Luma Update Guest Status',
|
||||
description:
|
||||
"Update a guest's approval status on a Luma event — approve, decline, waitlist, or set to pending. Identify the guest by email or guest ID.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Luma API key',
|
||||
},
|
||||
eventId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Event ID the guest belongs to (starts with evt-)',
|
||||
},
|
||||
guestIdentifier: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
"Guest email address or guest ID (gst-...). Values containing '@' are treated as emails; otherwise as a guest ID.",
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New approval status: approved, declined, pending_approval, or waitlist',
|
||||
},
|
||||
shouldRefund: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Refund a paid guest when moving them out of an approved state (defaults to false)',
|
||||
},
|
||||
sendEmail: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to email the guest about the status change (defaults to true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://public-api.luma.com/v1/event/update-guest-status',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'x-luma-api-key': params.apiKey,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const identifier = params.guestIdentifier.trim()
|
||||
const guest = identifier.includes('@')
|
||||
? { type: 'email', email: identifier }
|
||||
: { type: 'api_id', api_id: identifier }
|
||||
const body: Record<string, unknown> = {
|
||||
guest,
|
||||
status: params.status,
|
||||
}
|
||||
if (params.eventId) body.event_id = params.eventId.trim()
|
||||
if (params.shouldRefund !== undefined) body.should_refund = params.shouldRefund
|
||||
if (params.sendEmail !== undefined) body.send_email = params.sendEmail
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || data.error || 'Failed to update guest status')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: params?.status ?? '',
|
||||
guest: params?.guestIdentifier?.trim() ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'The approval status applied to the guest',
|
||||
},
|
||||
guest: {
|
||||
type: 'string',
|
||||
description: 'The guest identifier (email or ID) that was updated',
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user