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,104 @@
|
||||
import type { GrainCreateHookParams, GrainCreateHookResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainCreateHookTool: ToolConfig<GrainCreateHookParams, GrainCreateHookResponse> = {
|
||||
id: 'grain_create_hook',
|
||||
name: 'Grain Create Webhook',
|
||||
description: 'Create a webhook to receive recording events',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
hookUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Webhook endpoint URL (e.g., "https://example.com/webhooks/grain")',
|
||||
},
|
||||
viewId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Grain view ID from GET /_/public-api/views',
|
||||
},
|
||||
actions: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional list of actions to subscribe to: added, updated, removed',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.grain.com/_/public-api/hooks',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
version: 2,
|
||||
hook_url: params.hookUrl,
|
||||
view_id: params.viewId,
|
||||
}
|
||||
if (params.actions && params.actions.length > 0) {
|
||||
body.actions = params.actions
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to create webhook')
|
||||
}
|
||||
|
||||
if (!data?.id) {
|
||||
throw new Error('Grain webhook created but response did not include a webhook id')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: data,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Hook UUID',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether hook is active',
|
||||
},
|
||||
hook_url: {
|
||||
type: 'string',
|
||||
description: 'The webhook URL',
|
||||
},
|
||||
view_id: {
|
||||
type: 'string',
|
||||
description: 'Grain view ID for the webhook',
|
||||
},
|
||||
actions: {
|
||||
type: 'array',
|
||||
description: 'Configured actions for the webhook',
|
||||
},
|
||||
inserted_at: {
|
||||
type: 'string',
|
||||
description: 'ISO8601 creation timestamp',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { GrainDeleteHookParams, GrainDeleteHookResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainDeleteHookTool: ToolConfig<GrainDeleteHookParams, GrainDeleteHookResponse> = {
|
||||
id: 'grain_delete_hook',
|
||||
name: 'Grain Delete Webhook',
|
||||
description: 'Delete a webhook by ID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
hookId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The hook UUID to delete (e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.grain.com/_/public-api/hooks/${params.hookId}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
throw new Error(data.error || data.message || 'Failed to delete webhook')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'True when webhook was successfully deleted',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import type { GrainGetRecordingParams, GrainGetRecordingResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainGetRecordingTool: ToolConfig<GrainGetRecordingParams, GrainGetRecordingResponse> =
|
||||
{
|
||||
id: 'grain_get_recording',
|
||||
name: 'Grain Get Recording',
|
||||
description: 'Get details of a single recording by ID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
recordingId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The recording UUID (e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890")',
|
||||
},
|
||||
includeHighlights: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include highlights/clips',
|
||||
},
|
||||
includeParticipants: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include participant list',
|
||||
},
|
||||
includeAiSummary: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include AI summary',
|
||||
},
|
||||
includeCalendarEvent: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include calendar event data',
|
||||
},
|
||||
includeHubspot: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include HubSpot associations',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.grain.com/_/public-api/v2/recordings/${params.recordingId}`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Public-Api-Version': '2025-10-31',
|
||||
}),
|
||||
body: (params) => {
|
||||
const include: Record<string, any> = {}
|
||||
|
||||
if (params.includeHighlights) {
|
||||
include.highlights = true
|
||||
}
|
||||
if (params.includeParticipants) {
|
||||
include.participants = true
|
||||
}
|
||||
if (params.includeAiSummary) {
|
||||
include.ai_summary = true
|
||||
}
|
||||
if (params.includeCalendarEvent) {
|
||||
include.calendar_event = true
|
||||
}
|
||||
if (params.includeHubspot) {
|
||||
include.hubspot = true
|
||||
}
|
||||
|
||||
if (Object.keys(include).length > 0) {
|
||||
return { include }
|
||||
}
|
||||
return {}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to get recording')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: data,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Recording UUID',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: 'Recording title',
|
||||
},
|
||||
start_datetime: {
|
||||
type: 'string',
|
||||
description: 'ISO8601 start timestamp',
|
||||
},
|
||||
end_datetime: {
|
||||
type: 'string',
|
||||
description: 'ISO8601 end timestamp',
|
||||
},
|
||||
duration_ms: {
|
||||
type: 'number',
|
||||
description: 'Duration in milliseconds',
|
||||
},
|
||||
media_type: {
|
||||
type: 'string',
|
||||
description: 'audio, transcript, or video',
|
||||
},
|
||||
source: {
|
||||
type: 'string',
|
||||
description: 'Recording source (zoom, meet, teams, etc.)',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'URL to view in Grain',
|
||||
},
|
||||
thumbnail_url: {
|
||||
type: 'string',
|
||||
description: 'Thumbnail image URL',
|
||||
optional: true,
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Array of tag strings',
|
||||
},
|
||||
teams: {
|
||||
type: 'array',
|
||||
description: 'Teams the recording belongs to',
|
||||
},
|
||||
meeting_type: {
|
||||
type: 'object',
|
||||
description: 'Meeting type info (id, name, scope)',
|
||||
optional: true,
|
||||
},
|
||||
highlights: {
|
||||
type: 'array',
|
||||
description: 'Highlights (if included)',
|
||||
optional: true,
|
||||
},
|
||||
participants: {
|
||||
type: 'array',
|
||||
description: 'Participants (if included)',
|
||||
optional: true,
|
||||
},
|
||||
ai_summary: {
|
||||
type: 'object',
|
||||
description: 'AI summary text (if included)',
|
||||
optional: true,
|
||||
},
|
||||
calendar_event: {
|
||||
type: 'object',
|
||||
description: 'Calendar event data (if included)',
|
||||
optional: true,
|
||||
},
|
||||
hubspot: {
|
||||
type: 'object',
|
||||
description: 'HubSpot associations (if included)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { GrainGetTranscriptParams, GrainGetTranscriptResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainGetTranscriptTool: ToolConfig<
|
||||
GrainGetTranscriptParams,
|
||||
GrainGetTranscriptResponse
|
||||
> = {
|
||||
id: 'grain_get_transcript',
|
||||
name: 'Grain Get Transcript',
|
||||
description: 'Get the full transcript of a recording',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
recordingId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The recording UUID (e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.grain.com/_/public-api/v2/recordings/${params.recordingId}/transcript`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Public-Api-Version': '2025-10-31',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to get transcript')
|
||||
}
|
||||
|
||||
// API returns array directly
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
transcript: Array.isArray(data) ? data : [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
transcript: {
|
||||
type: 'array',
|
||||
description: 'Array of transcript sections',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
participant_id: { type: 'string', description: 'Participant UUID (nullable)' },
|
||||
speaker: { type: 'string', description: 'Speaker name' },
|
||||
start: { type: 'number', description: 'Start timestamp in ms' },
|
||||
end: { type: 'number', description: 'End timestamp in ms' },
|
||||
text: { type: 'string', description: 'Transcript text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export { grainCreateHookTool } from './create_hook'
|
||||
export { grainDeleteHookTool } from './delete_hook'
|
||||
export { grainGetRecordingTool } from './get_recording'
|
||||
export { grainGetTranscriptTool } from './get_transcript'
|
||||
export { grainListHooksTool } from './list_hooks'
|
||||
export { grainListMeetingTypesTool } from './list_meeting_types'
|
||||
export { grainListRecordingsTool } from './list_recordings'
|
||||
export { grainListTeamsTool } from './list_teams'
|
||||
export { grainListViewsTool } from './list_views'
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { GrainListHooksParams, GrainListHooksResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainListHooksTool: ToolConfig<GrainListHooksParams, GrainListHooksResponse> = {
|
||||
id: 'grain_list_hooks',
|
||||
name: 'Grain List Webhooks',
|
||||
description: 'List all webhooks for the account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.grain.com/_/public-api/hooks',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to list webhooks')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
hooks: data.hooks || data || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
hooks: {
|
||||
type: 'array',
|
||||
description: 'Array of hook objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Hook UUID' },
|
||||
enabled: { type: 'boolean', description: 'Whether hook is active' },
|
||||
hook_url: { type: 'string', description: 'Webhook URL' },
|
||||
view_id: { type: 'string', description: 'Grain view ID' },
|
||||
actions: { type: 'array', description: 'Configured actions' },
|
||||
inserted_at: { type: 'string', description: 'Creation timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type {
|
||||
GrainListMeetingTypesParams,
|
||||
GrainListMeetingTypesResponse,
|
||||
} from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainListMeetingTypesTool: ToolConfig<
|
||||
GrainListMeetingTypesParams,
|
||||
GrainListMeetingTypesResponse
|
||||
> = {
|
||||
id: 'grain_list_meeting_types',
|
||||
name: 'Grain List Meeting Types',
|
||||
description: 'List all meeting types in the workspace',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.grain.com/_/public-api/v2/meeting_types',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Public-Api-Version': '2025-10-31',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to list meeting types')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
meeting_types: data.meeting_types || data || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
meeting_types: {
|
||||
type: 'array',
|
||||
description: 'Array of meeting type objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Meeting type UUID' },
|
||||
name: { type: 'string', description: 'Meeting type name' },
|
||||
scope: { type: 'string', description: 'internal or external' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import type { GrainListRecordingsParams, GrainListRecordingsResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainListRecordingsTool: ToolConfig<
|
||||
GrainListRecordingsParams,
|
||||
GrainListRecordingsResponse
|
||||
> = {
|
||||
id: 'grain_list_recordings',
|
||||
name: 'Grain List Recordings',
|
||||
description: 'List recordings from Grain with optional filters and pagination',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination cursor for next page (returned from previous response)',
|
||||
},
|
||||
beforeDatetime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only recordings before this ISO8601 timestamp (e.g., "2024-01-15T00:00:00Z")',
|
||||
},
|
||||
afterDatetime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only recordings after this ISO8601 timestamp (e.g., "2024-01-01T00:00:00Z")',
|
||||
},
|
||||
participantScope: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter: "internal" or "external"',
|
||||
},
|
||||
titleSearch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search term to filter by recording title (e.g., "weekly standup")',
|
||||
},
|
||||
teamId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by team UUID (e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890")',
|
||||
},
|
||||
meetingTypeId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by meeting type UUID (e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890")',
|
||||
},
|
||||
includeHighlights: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include highlights/clips in response',
|
||||
},
|
||||
includeParticipants: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include participant list in response',
|
||||
},
|
||||
includeAiSummary: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Include AI-generated summary',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.grain.com/_/public-api/v2/recordings',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Public-Api-Version': '2025-10-31',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
|
||||
if (params.cursor) {
|
||||
body.cursor = params.cursor
|
||||
}
|
||||
|
||||
const filter: Record<string, any> = {}
|
||||
if (params.beforeDatetime) {
|
||||
filter.before_datetime = params.beforeDatetime
|
||||
}
|
||||
if (params.afterDatetime) {
|
||||
filter.after_datetime = params.afterDatetime
|
||||
}
|
||||
if (params.participantScope) {
|
||||
filter.participant_scope = params.participantScope
|
||||
}
|
||||
if (params.titleSearch) {
|
||||
filter.title_search = params.titleSearch
|
||||
}
|
||||
if (params.teamId) {
|
||||
filter.team = params.teamId
|
||||
}
|
||||
if (params.meetingTypeId) {
|
||||
filter.meeting_type = params.meetingTypeId
|
||||
}
|
||||
if (Object.keys(filter).length > 0) {
|
||||
body.filter = filter
|
||||
}
|
||||
|
||||
const include: Record<string, any> = {}
|
||||
if (params.includeHighlights) {
|
||||
include.highlights = true
|
||||
}
|
||||
if (params.includeParticipants) {
|
||||
include.participants = true
|
||||
}
|
||||
if (params.includeAiSummary) {
|
||||
include.ai_summary = true
|
||||
}
|
||||
if (Object.keys(include).length > 0) {
|
||||
body.include = include
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to list recordings')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
recordings: data.recordings || [],
|
||||
cursor: data.cursor || null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
recordings: {
|
||||
type: 'array',
|
||||
description: 'Array of recording objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Recording UUID' },
|
||||
title: { type: 'string', description: 'Recording title' },
|
||||
start_datetime: { type: 'string', description: 'ISO8601 start timestamp' },
|
||||
end_datetime: { type: 'string', description: 'ISO8601 end timestamp' },
|
||||
duration_ms: { type: 'number', description: 'Duration in milliseconds' },
|
||||
media_type: { type: 'string', description: 'audio, transcript, or video' },
|
||||
source: { type: 'string', description: 'Recording source' },
|
||||
url: { type: 'string', description: 'URL to view in Grain' },
|
||||
thumbnail_url: { type: 'string', description: 'Thumbnail URL' },
|
||||
tags: { type: 'array', description: 'Array of tags' },
|
||||
teams: { type: 'array', description: 'Teams the recording belongs to' },
|
||||
meeting_type: { type: 'object', description: 'Meeting type info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Cursor for next page (null if no more)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { GrainListTeamsParams, GrainListTeamsResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainListTeamsTool: ToolConfig<GrainListTeamsParams, GrainListTeamsResponse> = {
|
||||
id: 'grain_list_teams',
|
||||
name: 'Grain List Teams',
|
||||
description: 'List all teams in the workspace',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.grain.com/_/public-api/v2/teams',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Public-Api-Version': '2025-10-31',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to list teams')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
teams: data.teams || data || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
teams: {
|
||||
type: 'array',
|
||||
description: 'Array of team objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Team UUID' },
|
||||
name: { type: 'string', description: 'Team name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { GrainListViewsParams, GrainListViewsResponse } from '@/tools/grain/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const grainListViewsTool: ToolConfig<GrainListViewsParams, GrainListViewsResponse> = {
|
||||
id: 'grain_list_views',
|
||||
name: 'Grain List Views',
|
||||
description: 'List available Grain views for webhook subscriptions',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Grain API key (Personal Access Token)',
|
||||
},
|
||||
typeFilter: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional view type filter: recordings, highlights, or stories',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
params.typeFilter
|
||||
? `https://api.grain.com/_/public-api/views?type_filter=${encodeURIComponent(params.typeFilter)}`
|
||||
: 'https://api.grain.com/_/public-api/views',
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || data.message || 'Failed to list views')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
views: data.views || data || [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
views: {
|
||||
type: 'array',
|
||||
description: 'Array of Grain views',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'View UUID' },
|
||||
name: { type: 'string', description: 'View name' },
|
||||
type: { type: 'string', description: 'View type: recordings, highlights, or stories' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Grain API Types
|
||||
* Base URL: https://api.grain.com/_/public-api
|
||||
* API Version: 2025-10-31
|
||||
*/
|
||||
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
interface GrainTeam {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
interface GrainMeetingType {
|
||||
id: string
|
||||
name: string
|
||||
scope: 'internal' | 'external'
|
||||
}
|
||||
|
||||
interface GrainParticipant {
|
||||
id: string
|
||||
name: string
|
||||
email: string | null
|
||||
scope: 'internal' | 'external' | 'unknown'
|
||||
confirmed_attendee: boolean
|
||||
hs_contact_id?: string | null
|
||||
}
|
||||
|
||||
interface GrainHighlight {
|
||||
id: string
|
||||
recording_id: string
|
||||
text: string
|
||||
transcript: string
|
||||
speakers: string[]
|
||||
timestamp: number
|
||||
duration: number
|
||||
tags: string[]
|
||||
url: string
|
||||
thumbnail_url: string
|
||||
created_datetime: string
|
||||
}
|
||||
|
||||
interface GrainAiSummary {
|
||||
text: string
|
||||
}
|
||||
|
||||
interface GrainCalendarEvent {
|
||||
ical_uid: string
|
||||
}
|
||||
|
||||
interface GrainHubspotData {
|
||||
hubspot_company_ids: string[]
|
||||
hubspot_deal_ids: string[]
|
||||
}
|
||||
|
||||
interface GrainAiTemplateSection {
|
||||
title: string
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
interface GrainPrivateNotes {
|
||||
text: string
|
||||
}
|
||||
|
||||
interface GrainTranscriptSection {
|
||||
participant_id: string | null
|
||||
speaker: string
|
||||
start: number
|
||||
end: number
|
||||
text: string
|
||||
}
|
||||
|
||||
interface GrainRecording {
|
||||
id: string
|
||||
title: string
|
||||
start_datetime: string
|
||||
end_datetime: string
|
||||
duration_ms: number
|
||||
media_type: 'audio' | 'transcript' | 'video'
|
||||
source: 'aircall' | 'local_capture' | 'meet' | 'teams' | 'upload' | 'webex' | 'zoom' | 'other'
|
||||
url: string
|
||||
thumbnail_url: string | null
|
||||
tags: string[]
|
||||
teams: GrainTeam[]
|
||||
meeting_type: GrainMeetingType | null
|
||||
highlights?: GrainHighlight[]
|
||||
participants?: GrainParticipant[]
|
||||
ai_summary?: GrainAiSummary
|
||||
calendar_event?: GrainCalendarEvent | null
|
||||
hubspot?: GrainHubspotData
|
||||
private_notes?: GrainPrivateNotes | null
|
||||
ai_template_sections?: GrainAiTemplateSection[]
|
||||
}
|
||||
|
||||
interface GrainHook {
|
||||
id: string
|
||||
enabled: boolean
|
||||
version?: number
|
||||
hook_url: string
|
||||
view_id?: string
|
||||
actions?: Array<'added' | 'updated' | 'removed'>
|
||||
inserted_at: string
|
||||
}
|
||||
|
||||
interface GrainView {
|
||||
id: string
|
||||
name?: string
|
||||
type?: 'recordings' | 'highlights' | 'stories'
|
||||
}
|
||||
|
||||
export interface GrainListViewsParams {
|
||||
apiKey: string
|
||||
typeFilter?: 'recordings' | 'highlights' | 'stories'
|
||||
}
|
||||
|
||||
export interface GrainListViewsResponse extends ToolResponse {
|
||||
output: {
|
||||
views: GrainView[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrainListRecordingsParams {
|
||||
apiKey: string
|
||||
cursor?: string
|
||||
beforeDatetime?: string
|
||||
afterDatetime?: string
|
||||
participantScope?: 'internal' | 'external'
|
||||
titleSearch?: string
|
||||
teamId?: string
|
||||
meetingTypeId?: string
|
||||
includeHighlights?: boolean
|
||||
includeParticipants?: boolean
|
||||
includeAiSummary?: boolean
|
||||
}
|
||||
|
||||
export interface GrainListRecordingsResponse extends ToolResponse {
|
||||
output: {
|
||||
recordings: GrainRecording[]
|
||||
cursor: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrainGetRecordingParams {
|
||||
apiKey: string
|
||||
recordingId: string
|
||||
includeHighlights?: boolean
|
||||
includeParticipants?: boolean
|
||||
includeAiSummary?: boolean
|
||||
includeCalendarEvent?: boolean
|
||||
includeHubspot?: boolean
|
||||
}
|
||||
|
||||
export interface GrainGetRecordingResponse extends ToolResponse {
|
||||
output: GrainRecording
|
||||
}
|
||||
|
||||
export interface GrainGetTranscriptParams {
|
||||
apiKey: string
|
||||
recordingId: string
|
||||
}
|
||||
|
||||
export interface GrainGetTranscriptResponse extends ToolResponse {
|
||||
output: {
|
||||
transcript: GrainTranscriptSection[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrainListTeamsParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface GrainListTeamsResponse extends ToolResponse {
|
||||
output: {
|
||||
teams: GrainTeam[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrainListMeetingTypesParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface GrainListMeetingTypesResponse extends ToolResponse {
|
||||
output: {
|
||||
meeting_types: GrainMeetingType[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrainCreateHookParams {
|
||||
apiKey: string
|
||||
hookUrl: string
|
||||
viewId: string
|
||||
actions?: Array<'added' | 'updated' | 'removed'>
|
||||
}
|
||||
|
||||
export interface GrainCreateHookResponse extends ToolResponse {
|
||||
output: GrainHook
|
||||
}
|
||||
|
||||
export interface GrainListHooksParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface GrainListHooksResponse extends ToolResponse {
|
||||
output: {
|
||||
hooks: GrainHook[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface GrainDeleteHookParams {
|
||||
apiKey: string
|
||||
hookId: string
|
||||
}
|
||||
|
||||
export interface GrainDeleteHookResponse extends ToolResponse {
|
||||
output: {
|
||||
success: true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user