diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index d28d20aa6d..5bfc1853a5 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2132,6 +2132,18 @@ type FieldConnection { edges: [FieldEdge!]! } +type AppConnection { + id: ID! + providerName: String! + name: String! + handle: String! + visibility: String! + userWorkspaceId: String! + accessToken: String! + scopes: [String!]! + authFailedAt: String +} + type ResendEmailVerificationToken { success: Boolean! } @@ -2529,6 +2541,12 @@ type AgentMessagePart { createdAt: DateTime! } +type RunAgentResult { + result: JSON + error: String + success: Boolean! +} + type ChannelSyncSuccess { success: Boolean! } @@ -3011,6 +3029,8 @@ type Query { myMessageChannels(connectedAccountId: UUID): [MessageChannel!]! myCalendarChannels(connectedAccountId: UUID): [CalendarChannel!]! minimalMetadata: MinimalMetadata! + appConnections(filter: ListAppConnectionsInput): [AppConnection!]! + appConnection(id: ID!): AppConnection! findWorkspaceAiStats: WorkspaceAiStats! chatThreads: [AgentChatThread!]! chatThread(id: UUID!): AgentChatThread! @@ -3065,6 +3085,12 @@ input AgentIdInput { id: UUID! } +input ListAppConnectionsInput { + providerName: String + userWorkspaceId: String + visibility: String +} + input EventLogQueryInput { table: EventLogTable! filters: EventLogFiltersInput @@ -3238,6 +3264,7 @@ type Mutation { upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult! assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean! removeRoleFromAgent(agentId: UUID!): Boolean! + runAgent(input: RunAgentInput!): RunAgentResult! createWebhook(input: CreateWebhookInput!): Webhook! updateWebhook(input: UpdateWebhookInput!): Webhook! deleteWebhook(id: UUID!): Webhook! @@ -4093,6 +4120,11 @@ input RowLevelPermissionPredicateGroupInput { positionInRowLevelPermissionPredicateGroup: Float } +input RunAgentInput { + agentUniversalIdentifier: String! + prompt: String! +} + input CreateWebhookInput { id: UUID targetUrl: String! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index eb1c33c4bc..bd7d9ebf4a 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1762,6 +1762,19 @@ export interface FieldConnection { __typename: 'FieldConnection' } +export interface AppConnection { + id: Scalars['ID'] + providerName: Scalars['String'] + name: Scalars['String'] + handle: Scalars['String'] + visibility: Scalars['String'] + userWorkspaceId: Scalars['String'] + accessToken: Scalars['String'] + scopes: Scalars['String'][] + authFailedAt?: Scalars['String'] + __typename: 'AppConnection' +} + export interface ResendEmailVerificationToken { success: Scalars['Boolean'] __typename: 'ResendEmailVerificationToken' @@ -2211,6 +2224,13 @@ export interface AgentMessagePart { __typename: 'AgentMessagePart' } +export interface RunAgentResult { + result?: Scalars['JSON'] + error?: Scalars['String'] + success: Scalars['Boolean'] + __typename: 'RunAgentResult' +} + export interface ChannelSyncSuccess { success: Scalars['Boolean'] __typename: 'ChannelSyncSuccess' @@ -2607,6 +2627,8 @@ export interface Query { myMessageChannels: MessageChannel[] myCalendarChannels: CalendarChannel[] minimalMetadata: MinimalMetadata + appConnections: AppConnection[] + appConnection: AppConnection findWorkspaceAiStats: WorkspaceAiStats chatThreads: AgentChatThread[] chatThread: AgentChatThread @@ -2767,6 +2789,7 @@ export interface Mutation { upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult assignRoleToAgent: Scalars['Boolean'] removeRoleFromAgent: Scalars['Boolean'] + runAgent: RunAgentResult createWebhook: Webhook updateWebhook: Webhook deleteWebhook: Webhook @@ -4722,6 +4745,20 @@ export interface FieldConnectionGenqlSelection{ __scalar?: boolean | number } +export interface AppConnectionGenqlSelection{ + id?: boolean | number + providerName?: boolean | number + name?: boolean | number + handle?: boolean | number + visibility?: boolean | number + userWorkspaceId?: boolean | number + accessToken?: boolean | number + scopes?: boolean | number + authFailedAt?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface ResendEmailVerificationTokenGenqlSelection{ success?: boolean | number __typename?: boolean | number @@ -5226,6 +5263,14 @@ export interface AgentMessagePartGenqlSelection{ __scalar?: boolean | number } +export interface RunAgentResultGenqlSelection{ + result?: boolean | number + error?: boolean | number + success?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface ChannelSyncSuccessGenqlSelection{ success?: boolean | number __typename?: boolean | number @@ -5646,6 +5691,8 @@ export interface QueryGenqlSelection{ myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) myCalendarChannels?: (CalendarChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) minimalMetadata?: MinimalMetadataGenqlSelection + appConnections?: (AppConnectionGenqlSelection & { __args?: {filter?: (ListAppConnectionsInput | null)} }) + appConnection?: (AppConnectionGenqlSelection & { __args: {id: Scalars['ID']} }) findWorkspaceAiStats?: WorkspaceAiStatsGenqlSelection chatThreads?: AgentChatThreadGenqlSelection chatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID']} }) @@ -5698,6 +5745,8 @@ export interface AgentIdInput { /** The id of the agent. */ id: Scalars['UUID']} +export interface ListAppConnectionsInput {providerName?: (Scalars['String'] | null),userWorkspaceId?: (Scalars['String'] | null),visibility?: (Scalars['String'] | null)} + export interface EventLogQueryInput {table: EventLogTable,filters?: (EventLogFiltersInput | null),first?: (Scalars['Int'] | null),after?: (Scalars['String'] | null)} export interface EventLogFiltersInput {eventType?: (Scalars['String'] | null),userWorkspaceId?: (Scalars['String'] | null),dateRange?: (EventLogDateRangeInput | null),recordId?: (Scalars['String'] | null),objectMetadataId?: (Scalars['String'] | null)} @@ -5827,6 +5876,7 @@ export interface MutationGenqlSelection{ upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} }) assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} } removeRoleFromAgent?: { __args: {agentId: Scalars['UUID']} } + runAgent?: (RunAgentResultGenqlSelection & { __args: {input: RunAgentInput} }) createWebhook?: (WebhookGenqlSelection & { __args: {input: CreateWebhookInput} }) updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} }) deleteWebhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} }) @@ -6184,6 +6234,8 @@ export interface RowLevelPermissionPredicateInput {id?: (Scalars['UUID'] | null) export interface RowLevelPermissionPredicateGroupInput {id?: (Scalars['UUID'] | null),objectMetadataId: Scalars['UUID'],parentRowLevelPermissionPredicateGroupId?: (Scalars['UUID'] | null),logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator,positionInRowLevelPermissionPredicateGroup?: (Scalars['Float'] | null)} +export interface RunAgentInput {agentUniversalIdentifier: Scalars['String'],prompt: Scalars['String']} + export interface CreateWebhookInput {id?: (Scalars['UUID'] | null),targetUrl: Scalars['String'],operations: Scalars['String'][],description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)} export interface UpdateWebhookInput { @@ -7535,6 +7587,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const AppConnection_possibleTypes: string[] = ['AppConnection'] + export const isAppConnection = (obj?: { __typename?: any } | null): obj is AppConnection => { + if (!obj?.__typename) throw new Error('__typename is missing in "isAppConnection"') + return AppConnection_possibleTypes.includes(obj.__typename) + } + + + const ResendEmailVerificationToken_possibleTypes: string[] = ['ResendEmailVerificationToken'] export const isResendEmailVerificationToken = (obj?: { __typename?: any } | null): obj is ResendEmailVerificationToken => { if (!obj?.__typename) throw new Error('__typename is missing in "isResendEmailVerificationToken"') @@ -7991,6 +8051,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const RunAgentResult_possibleTypes: string[] = ['RunAgentResult'] + export const isRunAgentResult = (obj?: { __typename?: any } | null): obj is RunAgentResult => { + if (!obj?.__typename) throw new Error('__typename is missing in "isRunAgentResult"') + return RunAgentResult_possibleTypes.includes(obj.__typename) + } + + + const ChannelSyncSuccess_possibleTypes: string[] = ['ChannelSyncSuccess'] export const isChannelSyncSuccess = (obj?: { __typename?: any } | null): obj is ChannelSyncSuccess => { if (!obj?.__typename) throw new Error('__typename is missing in "isChannelSyncSuccess"') diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index c676ed29bf..1f21251391 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -60,27 +60,27 @@ export default { 190, 193, 206, - 231, - 294, - 304, - 305, + 221, + 233, 306, 307, + 308, 309, - 310, 311, 312, 313, 314, 315, - 318, + 316, + 317, 320, - 329, - 336, - 343, - 379, - 461, - 473 + 322, + 332, + 339, + 346, + 382, + 465, + 477 ], "types": { "BillingProductDTO": { @@ -1895,7 +1895,7 @@ export default { 151 ], "billingEntitlements": [ - 230 + 232 ], "hasValidEnterpriseKey": [ 6 @@ -1996,7 +1996,7 @@ export default { 20 ], "deletedWorkspaceMembers": [ - 229 + 231 ], "hasPassword": [ 6 @@ -2008,7 +2008,7 @@ export default { 17 ], "availableWorkspaces": [ - 228 + 230 ], "__typename": [ 1 @@ -4106,6 +4106,39 @@ export default { 1 ] }, + "AppConnection": { + "id": [ + 221 + ], + "providerName": [ + 1 + ], + "name": [ + 1 + ], + "handle": [ + 1 + ], + "visibility": [ + 1 + ], + "userWorkspaceId": [ + 1 + ], + "accessToken": [ + 1 + ], + "scopes": [ + 1 + ], + "authFailedAt": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "ID": {}, "ResendEmailVerificationToken": { "success": [ 6 @@ -4170,7 +4203,7 @@ export default { 180 ], "workspace": [ - 223 + 225 ], "__typename": [ 1 @@ -4239,7 +4272,7 @@ export default { 1 ], "sso": [ - 226 + 228 ], "__typename": [ 1 @@ -4247,10 +4280,10 @@ export default { }, "AvailableWorkspaces": { "availableWorkspacesForSignIn": [ - 227 + 229 ], "availableWorkspacesForSignUp": [ - 227 + 229 ], "__typename": [ 1 @@ -4278,7 +4311,7 @@ export default { }, "BillingEntitlement": { "key": [ - 231 + 233 ], "value": [ 6 @@ -4316,7 +4349,7 @@ export default { 1 ], "records": [ - 232 + 234 ], "__typename": [ 1 @@ -4386,10 +4419,10 @@ export default { }, "AvailableWorkspacesAndAccessTokens": { "tokens": [ - 240 + 242 ], "availableWorkspaces": [ - 228 + 230 ], "__typename": [ 1 @@ -4441,7 +4474,7 @@ export default { 32 ], "workspace": [ - 245 + 247 ], "__typename": [ 1 @@ -4490,7 +4523,7 @@ export default { }, "AuthTokens": { "tokens": [ - 240 + 242 ], "__typename": [ 1 @@ -4531,7 +4564,7 @@ export default { 32 ], "workspace": [ - 245 + 247 ], "__typename": [ 1 @@ -4553,7 +4586,7 @@ export default { 1 ], "dailyUsage": [ - 256 + 258 ], "__typename": [ 1 @@ -4570,7 +4603,7 @@ export default { 199 ], "timeSeries": [ - 256 + 258 ], "periodStart": [ 4 @@ -4579,7 +4612,7 @@ export default { 4 ], "userDailyUsage": [ - 257 + 259 ], "__typename": [ 1 @@ -4744,7 +4777,7 @@ export default { 1 ], "location": [ - 266 + 268 ], "__typename": [ 1 @@ -4769,13 +4802,13 @@ export default { }, "PublicImapSmtpCaldavConnectionParameters": { "IMAP": [ - 268 + 270 ], "SMTP": [ - 268 + 270 ], "CALDAV": [ - 268 + 270 ], "__typename": [ 1 @@ -4828,7 +4861,7 @@ export default { 4 ], "connectionParameters": [ - 269 + 271 ], "__typename": [ 1 @@ -4853,13 +4886,13 @@ export default { }, "ImapSmtpCaldavPublicConnectionParameters": { "IMAP": [ - 271 + 273 ], "SMTP": [ - 271 + 273 ], "CALDAV": [ - 271 + 273 ], "__typename": [ 1 @@ -4879,7 +4912,7 @@ export default { 3 ], "connectionParameters": [ - 272 + 274 ], "__typename": [ 1 @@ -5037,6 +5070,20 @@ export default { 1 ] }, + "RunAgentResult": { + "result": [ + 15 + ], + "error": [ + 1 + ], + "success": [ + 6 + ], + "__typename": [ + 1 + ] + }, "ChannelSyncSuccess": { "success": [ 6 @@ -5067,7 +5114,7 @@ export default { 1 ], "series": [ - 279 + 282 ], "xAxisLabel": [ 1 @@ -5116,7 +5163,7 @@ export default { 1 ], "data": [ - 281 + 284 ], "__typename": [ 1 @@ -5124,7 +5171,7 @@ export default { }, "LineChartData": { "series": [ - 282 + 285 ], "xAxisLabel": [ 1 @@ -5161,7 +5208,7 @@ export default { }, "PieChartData": { "data": [ - 284 + 287 ], "showLegend": [ 6 @@ -5255,13 +5302,13 @@ export default { }, "EventLogQueryResult": { "records": [ - 288 + 291 ], "totalCount": [ 21 ], "pageInfo": [ - 289 + 292 ], "__typename": [ 1 @@ -5325,7 +5372,7 @@ export default { 1 ], "parts": [ - 277 + 279 ], "processedAt": [ 4 @@ -5339,7 +5386,7 @@ export default { }, "AgentChatThread": { "id": [ - 294 + 221 ], "title": [ 1 @@ -5378,7 +5425,6 @@ export default { 1 ] }, - "ID": {}, "AiSystemPromptSection": { "title": [ 1 @@ -5395,7 +5441,7 @@ export default { }, "AiSystemPromptPreview": { "sections": [ - 295 + 297 ], "estimatedTokenCount": [ 21 @@ -5471,10 +5517,10 @@ export default { 3 ], "evaluations": [ - 300 + 302 ], "messages": [ - 292 + 295 ], "createdAt": [ 4 @@ -5505,19 +5551,19 @@ export default { 1 ], "syncStatus": [ - 304 + 306 ], "syncStage": [ - 305 + 307 ], "visibility": [ - 306 + 308 ], "isContactAutoCreationEnabled": [ 6 ], "contactAutoCreationPolicy": [ - 307 + 309 ], "isSyncEnabled": [ 6 @@ -5553,22 +5599,22 @@ export default { 3 ], "visibility": [ - 309 + 311 ], "handle": [ 1 ], "type": [ - 310 + 312 ], "isContactAutoCreationEnabled": [ 6 ], "contactAutoCreationPolicy": [ - 311 + 313 ], "messageFolderImportPolicy": [ - 312 + 314 ], "excludeNonProfessionalEmails": [ 6 @@ -5577,7 +5623,7 @@ export default { 6 ], "pendingGroupEmailsAction": [ - 313 + 315 ], "isSyncEnabled": [ 6 @@ -5586,10 +5632,10 @@ export default { 4 ], "syncStatus": [ - 314 + 316 ], "syncStage": [ - 315 + 317 ], "syncStageStartedAt": [ 4 @@ -5610,7 +5656,7 @@ export default { 4 ], "connectedAccount": [ - 270 + 272 ], "__typename": [ 1 @@ -5625,7 +5671,7 @@ export default { "MessageChannelSyncStage": {}, "CreateEmailGroupChannelOutput": { "messageChannel": [ - 308 + 310 ], "forwardingAddress": [ 1 @@ -5654,7 +5700,7 @@ export default { 1 ], "pendingSyncAction": [ - 318 + 320 ], "messageChannelId": [ 3 @@ -5672,7 +5718,7 @@ export default { "MessageFolderPendingSyncAction": {}, "CollectionHash": { "collectionName": [ - 320 + 322 ], "hash": [ 1 @@ -5739,13 +5785,13 @@ export default { }, "MinimalMetadata": { "objectMetadataItems": [ - 321 + 323 ], "views": [ - 322 + 324 ], "collectionHashes": [ - 319 + 321 ], "__typename": [ 1 @@ -5898,7 +5944,7 @@ export default { 2, { "input": [ - 325, + 327, "GetApiKeyInput!" ] } @@ -5995,7 +6041,7 @@ export default { 41, { "input": [ - 326, + 328, "LogicFunctionIdInput!" ] } @@ -6007,7 +6053,7 @@ export default { 15, { "input": [ - 326, + 328, "LogicFunctionIdInput!" ] } @@ -6016,7 +6062,7 @@ export default { 1, { "input": [ - 326, + 328, "LogicFunctionIdInput!" ] } @@ -6099,19 +6145,19 @@ export default { 25, { "input": [ - 327, + 329, "AgentIdInput!" ] } ], "myConnectedAccounts": [ - 270 + 272 ], "getRoles": [ 29 ], "getToolIndex": [ - 276 + 278 ], "getToolInputSchema": [ 15, @@ -6123,10 +6169,10 @@ export default { } ], "webhooks": [ - 275 + 277 ], "webhook": [ - 275, + 277, { "id": [ 3, @@ -6174,7 +6220,7 @@ export default { } ], "myMessageFolders": [ - 317, + 319, { "messageChannelId": [ 3 @@ -6182,7 +6228,7 @@ export default { } ], "myMessageChannels": [ - 308, + 310, { "connectedAccountId": [ 3 @@ -6190,7 +6236,7 @@ export default { } ], "myCalendarChannels": [ - 303, + 305, { "connectedAccountId": [ 3 @@ -6198,16 +6244,33 @@ export default { } ], "minimalMetadata": [ - 323 + 325 + ], + "appConnections": [ + 220, + { + "filter": [ + 330 + ] + } + ], + "appConnection": [ + 220, + { + "id": [ + 221, + "ID!" + ] + } ], "findWorkspaceAiStats": [ - 302 + 304 ], "chatThreads": [ - 293 + 296 ], "chatThread": [ - 293, + 296, { "id": [ 3, @@ -6216,7 +6279,7 @@ export default { } ], "chatMessages": [ - 292, + 295, { "threadId": [ 3, @@ -6225,7 +6288,7 @@ export default { } ], "chatStreamCatchupChunks": [ - 297, + 299, { "threadId": [ 3, @@ -6234,13 +6297,13 @@ export default { } ], "getAiSystemPromptPreview": [ - 296 + 298 ], "skills": [ - 291 + 294 ], "skill": [ - 291, + 294, { "id": [ 3, @@ -6249,7 +6312,7 @@ export default { } ], "agentTurns": [ - 301, + 303, { "agentId": [ 3, @@ -6258,7 +6321,7 @@ export default { } ], "checkUserExists": [ - 253, + 255, { "email": [ 1, @@ -6270,7 +6333,7 @@ export default { } ], "checkWorkspaceInviteHashIsValid": [ - 254, + 256, { "inviteHash": [ 1, @@ -6288,7 +6351,7 @@ export default { } ], "validatePasswordResetToken": [ - 248, + 250, { "passwordResetToken": [ 1, @@ -6391,46 +6454,46 @@ export default { } ], "getSSOIdentityProviders": [ - 224 + 226 ], "eventLogs": [ - 290, + 293, { "input": [ - 328, + 331, "EventLogQueryInput!" ] } ], "pieChartData": [ - 285, + 288, { "input": [ - 332, + 335, "PieChartDataInput!" ] } ], "lineChartData": [ - 283, + 286, { "input": [ - 333, + 336, "LineChartDataInput!" ] } ], "barChartData": [ - 280, + 283, { "input": [ - 334, + 337, "BarChartDataInput!" ] } ], "getConnectedImapSmtpCaldavAccount": [ - 273, + 275, { "id": [ 3, @@ -6439,7 +6502,7 @@ export default { } ], "getAutoCompleteAddress": [ - 265, + 267, { "address": [ 1, @@ -6458,7 +6521,7 @@ export default { } ], "getAddressDetails": [ - 267, + 269, { "placeId": [ 1, @@ -6471,21 +6534,21 @@ export default { } ], "getUsageAnalytics": [ - 258, + 260, { "input": [ - 335 + 338 ] } ], "findManyPublicDomains": [ - 264 + 266 ], "findManyMarketplaceApps": [ - 262 + 264 ], "findMarketplaceAppDetail": [ - 263, + 265, { "universalIdentifier": [ 1, @@ -6507,7 +6570,7 @@ export default { }, "LogicFunctionIdInput": { "id": [ - 294 + 221 ], "__typename": [ 1 @@ -6521,12 +6584,26 @@ export default { 1 ] }, + "ListAppConnectionsInput": { + "providerName": [ + 1 + ], + "userWorkspaceId": [ + 1 + ], + "visibility": [ + 1 + ], + "__typename": [ + 1 + ] + }, "EventLogQueryInput": { "table": [ - 329 + 332 ], "filters": [ - 330 + 333 ], "first": [ 21 @@ -6547,7 +6624,7 @@ export default { 1 ], "dateRange": [ - 331 + 334 ], "recordId": [ 1 @@ -6614,7 +6691,7 @@ export default { 1 ], "operationTypes": [ - 336 + 339 ], "__typename": [ 1 @@ -6626,7 +6703,7 @@ export default { 6, { "input": [ - 338, + 341, "AddQuerySubscriptionInput!" ] } @@ -6635,7 +6712,7 @@ export default { 6, { "input": [ - 339, + 342, "RemoveQueryFromEventStreamInput!" ] } @@ -6644,7 +6721,7 @@ export default { 162, { "inputs": [ - 340, + 343, "[CreateNavigationMenuItemInput!]!" ] } @@ -6653,7 +6730,7 @@ export default { 162, { "input": [ - 340, + 343, "CreateNavigationMenuItemInput!" ] } @@ -6662,7 +6739,7 @@ export default { 162, { "inputs": [ - 341, + 344, "[UpdateOneNavigationMenuItemInput!]!" ] } @@ -6671,7 +6748,7 @@ export default { 162, { "input": [ - 341, + 344, "UpdateOneNavigationMenuItemInput!" ] } @@ -6698,7 +6775,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ] } @@ -6719,7 +6796,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ] } @@ -6728,7 +6805,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ] } @@ -6737,7 +6814,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ] } @@ -6746,7 +6823,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ] } @@ -6755,7 +6832,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ], "fieldMetadataId": [ @@ -6768,7 +6845,7 @@ export default { 136, { "file": [ - 343, + 346, "Upload!" ], "fieldMetadataUniversalIdentifier": [ @@ -6781,7 +6858,7 @@ export default { 62, { "input": [ - 344, + 347, "CreateViewFilterGroupInput!" ] } @@ -6794,7 +6871,7 @@ export default { "String!" ], "input": [ - 345, + 348, "UpdateViewFilterGroupInput!" ] } @@ -6821,7 +6898,7 @@ export default { 64, { "input": [ - 346, + 349, "CreateViewFilterInput!" ] } @@ -6830,7 +6907,7 @@ export default { 64, { "input": [ - 347, + 350, "UpdateViewFilterInput!" ] } @@ -6839,7 +6916,7 @@ export default { 64, { "input": [ - 349, + 352, "DeleteViewFilterInput!" ] } @@ -6848,7 +6925,7 @@ export default { 64, { "input": [ - 350, + 353, "DestroyViewFilterInput!" ] } @@ -6857,7 +6934,7 @@ export default { 70, { "input": [ - 351, + 354, "CreateViewInput!" ] } @@ -6870,7 +6947,7 @@ export default { "String!" ], "input": [ - 352, + 355, "UpdateViewInput!" ] } @@ -6897,7 +6974,7 @@ export default { 70, { "input": [ - 353, + 356, "UpsertViewWidgetInput!" ] } @@ -6906,7 +6983,7 @@ export default { 67, { "input": [ - 358, + 361, "CreateViewSortInput!" ] } @@ -6915,7 +6992,7 @@ export default { 67, { "input": [ - 359, + 362, "UpdateViewSortInput!" ] } @@ -6924,7 +7001,7 @@ export default { 6, { "input": [ - 361, + 364, "DeleteViewSortInput!" ] } @@ -6933,7 +7010,7 @@ export default { 6, { "input": [ - 362, + 365, "DestroyViewSortInput!" ] } @@ -6942,7 +7019,7 @@ export default { 60, { "input": [ - 363, + 366, "UpdateViewFieldInput!" ] } @@ -6951,7 +7028,7 @@ export default { 60, { "input": [ - 365, + 368, "CreateViewFieldInput!" ] } @@ -6960,7 +7037,7 @@ export default { 60, { "inputs": [ - 365, + 368, "[CreateViewFieldInput!]!" ] } @@ -6969,7 +7046,7 @@ export default { 60, { "input": [ - 366, + 369, "DeleteViewFieldInput!" ] } @@ -6978,7 +7055,7 @@ export default { 60, { "input": [ - 367, + 370, "DestroyViewFieldInput!" ] } @@ -6987,7 +7064,7 @@ export default { 69, { "input": [ - 368, + 371, "UpdateViewFieldGroupInput!" ] } @@ -6996,7 +7073,7 @@ export default { 69, { "input": [ - 370, + 373, "CreateViewFieldGroupInput!" ] } @@ -7005,7 +7082,7 @@ export default { 69, { "inputs": [ - 370, + 373, "[CreateViewFieldGroupInput!]!" ] } @@ -7014,7 +7091,7 @@ export default { 69, { "input": [ - 371, + 374, "DeleteViewFieldGroupInput!" ] } @@ -7023,7 +7100,7 @@ export default { 69, { "input": [ - 372, + 375, "DestroyViewFieldGroupInput!" ] } @@ -7032,7 +7109,7 @@ export default { 70, { "input": [ - 373, + 376, "UpsertFieldsWidgetInput!" ] } @@ -7041,7 +7118,7 @@ export default { 2, { "input": [ - 376, + 379, "CreateApiKeyInput!" ] } @@ -7050,7 +7127,7 @@ export default { 2, { "input": [ - 377, + 380, "UpdateApiKeyInput!" ] } @@ -7059,7 +7136,7 @@ export default { 2, { "input": [ - 378, + 381, "RevokeApiKeyInput!" ] } @@ -7101,7 +7178,7 @@ export default { 129, { "type": [ - 379, + 382, "AnalyticsType!" ], "name": [ @@ -7202,7 +7279,7 @@ export default { 135, { "input": [ - 380, + 383, "CreateApprovedAccessDomainInput!" ] } @@ -7211,7 +7288,7 @@ export default { 6, { "input": [ - 381, + 384, "DeleteApprovedAccessDomainInput!" ] } @@ -7220,7 +7297,7 @@ export default { 135, { "input": [ - 382, + 385, "ValidateApprovedAccessDomainInput!" ] } @@ -7229,7 +7306,7 @@ export default { 122, { "input": [ - 383, + 386, "CreatePageLayoutTabInput!" ] } @@ -7242,7 +7319,7 @@ export default { "String!" ], "input": [ - 384, + 387, "UpdatePageLayoutTabInput!" ] } @@ -7260,7 +7337,7 @@ export default { 123, { "input": [ - 385, + 388, "CreatePageLayoutInput!" ] } @@ -7273,7 +7350,7 @@ export default { "String!" ], "input": [ - 386, + 389, "UpdatePageLayoutInput!" ] } @@ -7295,7 +7372,7 @@ export default { "String!" ], "input": [ - 387, + 390, "UpdatePageLayoutWithTabsInput!" ] } @@ -7362,7 +7439,7 @@ export default { 134, { "input": [ - 391, + 394, "SendEmailViaDomainInput!" ] } @@ -7388,7 +7465,7 @@ export default { 85, { "input": [ - 392, + 395, "CreatePageLayoutWidgetInput!" ] } @@ -7401,7 +7478,7 @@ export default { "String!" ], "input": [ - 393, + 396, "UpdatePageLayoutWidgetInput!" ] } @@ -7419,7 +7496,7 @@ export default { 41, { "input": [ - 326, + 328, "LogicFunctionIdInput!" ] } @@ -7428,7 +7505,7 @@ export default { 41, { "input": [ - 394, + 397, "CreateLogicFunctionFromSourceInput!" ] } @@ -7437,7 +7514,7 @@ export default { 171, { "input": [ - 395, + 398, "ExecuteOneLogicFunctionInput!" ] } @@ -7446,7 +7523,7 @@ export default { 6, { "input": [ - 396, + 399, "UpdateLogicFunctionFromSourceInput!" ] } @@ -7455,7 +7532,7 @@ export default { 35, { "input": [ - 398, + 401, "CreateCommandMenuItemInput!" ] } @@ -7464,7 +7541,7 @@ export default { 35, { "input": [ - 399, + 402, "UpdateCommandMenuItemInput!" ] } @@ -7482,7 +7559,7 @@ export default { 34, { "input": [ - 400, + 403, "CreateFrontComponentInput!" ] } @@ -7491,7 +7568,7 @@ export default { 34, { "input": [ - 401, + 404, "UpdateFrontComponentInput!" ] } @@ -7509,7 +7586,7 @@ export default { 56, { "input": [ - 403, + 406, "CreateOneObjectInput!" ] } @@ -7518,7 +7595,7 @@ export default { 56, { "input": [ - 405, + 408, "DeleteOneObjectInput!" ] } @@ -7527,7 +7604,7 @@ export default { 56, { "input": [ - 406, + 409, "UpdateOneObjectInput!" ] } @@ -7536,7 +7613,7 @@ export default { 47, { "input": [ - 408, + 411, "CreateOneIndexInput!" ] } @@ -7545,7 +7622,7 @@ export default { 47, { "input": [ - 411, + 414, "DeleteOneIndexInput!" ] } @@ -7554,7 +7631,7 @@ export default { 25, { "input": [ - 412, + 415, "CreateAgentInput!" ] } @@ -7563,7 +7640,7 @@ export default { 25, { "input": [ - 413, + 416, "UpdateAgentInput!" ] } @@ -7572,13 +7649,13 @@ export default { 25, { "input": [ - 327, + 329, "AgentIdInput!" ] } ], "deleteConnectedAccount": [ - 270, + 272, { "id": [ 3, @@ -7603,7 +7680,7 @@ export default { 29, { "createRoleInput": [ - 414, + 417, "CreateRoleInput!" ] } @@ -7612,7 +7689,7 @@ export default { 29, { "updateRoleInput": [ - 415, + 418, "UpdateRoleInput!" ] } @@ -7630,7 +7707,7 @@ export default { 16, { "upsertObjectPermissionsInput": [ - 417, + 420, "UpsertObjectPermissionsInput!" ] } @@ -7639,7 +7716,7 @@ export default { 27, { "upsertPermissionFlagsInput": [ - 419, + 422, "UpsertPermissionFlagsInput!" ] } @@ -7648,16 +7725,16 @@ export default { 26, { "upsertFieldPermissionsInput": [ - 420, + 423, "UpsertFieldPermissionsInput!" ] } ], "upsertRowLevelPermissionPredicates": [ - 234, + 236, { "input": [ - 422, + 425, "UpsertRowLevelPermissionPredicatesInput!" ] } @@ -7684,26 +7761,35 @@ export default { ] } ], - "createWebhook": [ - 275, + "runAgent": [ + 280, { "input": [ - 425, + 428, + "RunAgentInput!" + ] + } + ], + "createWebhook": [ + 277, + { + "input": [ + 429, "CreateWebhookInput!" ] } ], "updateWebhook": [ - 275, + 277, { "input": [ - 426, + 430, "UpdateWebhookInput!" ] } ], "deleteWebhook": [ - 275, + 277, { "id": [ 3, @@ -7715,7 +7801,7 @@ export default { 44, { "input": [ - 428, + 432, "CreateOneFieldMetadataInput!" ] } @@ -7724,7 +7810,7 @@ export default { 44, { "input": [ - 430, + 434, "UpdateOneFieldMetadataInput!" ] } @@ -7733,7 +7819,7 @@ export default { 44, { "input": [ - 432, + 436, "DeleteOneFieldInput!" ] } @@ -7742,7 +7828,7 @@ export default { 66, { "input": [ - 433, + 437, "CreateViewGroupInput!" ] } @@ -7751,7 +7837,7 @@ export default { 66, { "inputs": [ - 433, + 437, "[CreateViewGroupInput!]!" ] } @@ -7760,7 +7846,7 @@ export default { 66, { "input": [ - 434, + 438, "UpdateViewGroupInput!" ] } @@ -7769,7 +7855,7 @@ export default { 66, { "inputs": [ - 434, + 438, "[UpdateViewGroupInput!]!" ] } @@ -7778,7 +7864,7 @@ export default { 66, { "input": [ - 436, + 440, "DeleteViewGroupInput!" ] } @@ -7787,49 +7873,49 @@ export default { 66, { "input": [ - 437, + 441, "DestroyViewGroupInput!" ] } ], "updateMessageFolder": [ - 317, + 319, { "input": [ - 438, + 442, "UpdateMessageFolderInput!" ] } ], "updateMessageFolders": [ - 317, + 319, { "input": [ - 440, + 444, "UpdateMessageFoldersInput!" ] } ], "updateMessageChannel": [ - 308, + 310, { "input": [ - 441, + 445, "UpdateMessageChannelInput!" ] } ], "createEmailGroupChannel": [ - 316, + 318, { "input": [ - 443, + 447, "CreateEmailGroupChannelInput!" ] } ], "deleteEmailGroupChannel": [ - 308, + 310, { "id": [ 3, @@ -7838,19 +7924,19 @@ export default { } ], "updateCalendarChannel": [ - 303, + 305, { "input": [ - 444, + 448, "UpdateCalendarChannelInput!" ] } ], "createChatThread": [ - 293 + 296 ], "sendChatMessage": [ - 298, + 300, { "threadId": [ 3, @@ -7871,7 +7957,7 @@ export default { 1 ], "fileAttachments": [ - 446, + 450, "[FileAttachmentInput!]" ] } @@ -7886,7 +7972,7 @@ export default { } ], "renameChatThread": [ - 293, + 296, { "id": [ 3, @@ -7899,7 +7985,7 @@ export default { } ], "archiveChatThread": [ - 293, + 296, { "id": [ 3, @@ -7908,7 +7994,7 @@ export default { } ], "unarchiveChatThread": [ - 293, + 296, { "id": [ 3, @@ -7935,25 +8021,25 @@ export default { } ], "createSkill": [ - 291, + 294, { "input": [ - 447, + 451, "CreateSkillInput!" ] } ], "updateSkill": [ - 291, + 294, { "input": [ - 448, + 452, "UpdateSkillInput!" ] } ], "deleteSkill": [ - 291, + 294, { "id": [ 3, @@ -7962,7 +8048,7 @@ export default { } ], "activateSkill": [ - 291, + 294, { "id": [ 3, @@ -7971,7 +8057,7 @@ export default { } ], "deactivateSkill": [ - 291, + 294, { "id": [ 3, @@ -7980,7 +8066,7 @@ export default { } ], "evaluateAgentTurn": [ - 300, + 302, { "turnId": [ 3, @@ -7989,7 +8075,7 @@ export default { } ], "runEvaluationInput": [ - 301, + 303, { "agentId": [ 3, @@ -8002,16 +8088,16 @@ export default { } ], "getAuthorizationUrlForSSO": [ - 243, + 245, { "input": [ - 449, + 453, "GetAuthorizationUrlForSSOInput!" ] } ], "getLoginTokenFromCredentials": [ - 252, + 254, { "email": [ 1, @@ -8037,7 +8123,7 @@ export default { } ], "signIn": [ - 241, + 243, { "email": [ 1, @@ -8059,7 +8145,7 @@ export default { } ], "verifyEmailAndGetLoginToken": [ - 249, + 251, { "emailVerificationToken": [ 1, @@ -8079,7 +8165,7 @@ export default { } ], "verifyEmailAndGetWorkspaceAgnosticToken": [ - 241, + 243, { "emailVerificationToken": [ 1, @@ -8095,7 +8181,7 @@ export default { } ], "getAuthTokensFromOTP": [ - 251, + 253, { "otp": [ 1, @@ -8115,7 +8201,7 @@ export default { } ], "signUp": [ - 241, + 243, { "email": [ 1, @@ -8137,7 +8223,7 @@ export default { } ], "signUpInWorkspace": [ - 246, + 248, { "email": [ 1, @@ -8168,13 +8254,13 @@ export default { } ], "signUpInNewWorkspace": [ - 246 + 248 ], "generateTransientToken": [ - 247 + 249 ], "getAuthTokensFromLoginToken": [ - 251, + 253, { "loginToken": [ 1, @@ -8187,7 +8273,7 @@ export default { } ], "authorizeApp": [ - 239, + 241, { "clientId": [ 1, @@ -8209,7 +8295,7 @@ export default { } ], "renewToken": [ - 251, + 253, { "appToken": [ 1, @@ -8218,7 +8304,7 @@ export default { } ], "generateApiKeyToken": [ - 250, + 252, { "apiKeyId": [ 3, @@ -8234,7 +8320,7 @@ export default { 32 ], "emailPasswordResetLink": [ - 242, + 244, { "email": [ 1, @@ -8246,7 +8332,7 @@ export default { } ], "updatePasswordViaResetToken": [ - 244, + 246, { "passwordResetToken": [ 1, @@ -8262,7 +8348,7 @@ export default { 202, { "input": [ - 450, + 454, "CreateApplicationRegistrationInput!" ] } @@ -8271,7 +8357,7 @@ export default { 7, { "input": [ - 451, + 455, "UpdateApplicationRegistrationInput!" ] } @@ -8298,7 +8384,7 @@ export default { 5, { "input": [ - 453, + 457, "CreateApplicationRegistrationVariableInput!" ] } @@ -8307,7 +8393,7 @@ export default { 5, { "input": [ - 454, + 458, "UpdateApplicationRegistrationVariableInput!" ] } @@ -8325,7 +8411,7 @@ export default { 7, { "file": [ - 343, + 346, "Upload!" ], "universalIdentifier": [ @@ -8347,7 +8433,7 @@ export default { } ], "initiateOTPProvisioning": [ - 237, + 239, { "loginToken": [ 1, @@ -8360,10 +8446,10 @@ export default { } ], "initiateOTPProvisioningForAuthenticatedUser": [ - 237 + 239 ], "deleteTwoFactorAuthenticationMethod": [ - 236, + 238, { "twoFactorAuthenticationMethodId": [ 3, @@ -8372,7 +8458,7 @@ export default { } ], "verifyTwoFactorAuthenticationMethodForAuthenticatedUser": [ - 238, + 240, { "otp": [ 1, @@ -8396,7 +8482,7 @@ export default { 6, { "input": [ - 456, + 460, "UpdateWorkspaceMemberSettingsInput!" ] } @@ -8414,7 +8500,7 @@ export default { } ], "resendEmailVerificationToken": [ - 220, + 222, { "email": [ 1, @@ -8430,7 +8516,7 @@ export default { 76, { "data": [ - 457, + 461, "ActivateWorkspaceInput!" ] } @@ -8439,7 +8525,7 @@ export default { 76, { "data": [ - 458, + 462, "UpdateWorkspaceInput!" ] } @@ -8448,13 +8534,13 @@ export default { 76 ], "checkCustomDomainValidRecords": [ - 233 + 235 ], "runWorkspaceMigration": [ 6, { "workspaceMigration": [ - 459, + 463, "WorkspaceMigrationInput!" ] } @@ -8469,43 +8555,43 @@ export default { } ], "createOIDCIdentityProvider": [ - 225, + 227, { "input": [ - 462, + 466, "SetupOIDCSsoInput!" ] } ], "createSAMLIdentityProvider": [ - 225, + 227, { "input": [ - 463, + 467, "SetupSAMLSsoInput!" ] } ], "deleteSSOIdentityProvider": [ - 221, + 223, { "input": [ - 464, + 468, "DeleteSsoInput!" ] } ], "editSSOIdentityProvider": [ - 222, + 224, { "input": [ - 465, + 469, "EditSsoInput!" ] } ], "duplicateDashboard": [ - 286, + 289, { "id": [ 3, @@ -8514,7 +8600,7 @@ export default { } ], "impersonate": [ - 255, + 257, { "userId": [ 3, @@ -8527,16 +8613,16 @@ export default { } ], "sendEmail": [ - 287, + 290, { "input": [ - 466, + 470, "SendEmailInput!" ] } ], "startChannelSync": [ - 278, + 281, { "connectedAccountId": [ 3, @@ -8545,14 +8631,14 @@ export default { } ], "saveImapSmtpCaldavAccount": [ - 274, + 276, { "handle": [ 1, "String!" ], "connectionParameters": [ - 468, + 472, "EmailAccountConnectionParameters!" ], "id": [ @@ -8564,13 +8650,13 @@ export default { 173, { "input": [ - 470, + 474, "UpdateLabPublicFeatureFlagInput!" ] } ], "createPublicDomain": [ - 264, + 266, { "domain": [ 1, @@ -8582,7 +8668,7 @@ export default { } ], "updatePublicDomain": [ - 264, + 266, { "domain": [ 1, @@ -8603,7 +8689,7 @@ export default { } ], "checkPublicDomainValidRecords": [ - 233, + 235, { "domain": [ 1, @@ -8615,7 +8701,7 @@ export default { 78, { "input": [ - 471, + 475, "CreateOneAppTokenInput!" ] } @@ -8648,7 +8734,7 @@ export default { 6 ], "createDevelopmentApplication": [ - 259, + 261, { "universalIdentifier": [ 1, @@ -8670,7 +8756,7 @@ export default { } ], "syncApplication": [ - 260, + 262, { "manifest": [ 15, @@ -8679,10 +8765,10 @@ export default { } ], "uploadApplicationFile": [ - 261, + 263, { "file": [ - 343, + 346, "Upload!" ], "applicationUniversalIdentifier": [ @@ -8690,7 +8776,7 @@ export default { "String!" ], "fileFolder": [ - 473, + 477, "FileFolder!" ], "filePath": [ @@ -8799,7 +8885,7 @@ export default { 3 ], "update": [ - 342 + 345 ], "__typename": [ 1 @@ -8909,7 +8995,7 @@ export default { 3 ], "update": [ - 348 + 351 ], "__typename": [ 1 @@ -9068,16 +9154,16 @@ export default { 3 ], "viewFields": [ - 354 + 357 ], "viewFilters": [ - 355 + 358 ], "viewFilterGroups": [ - 356 + 359 ], "viewSorts": [ - 357 + 360 ], "__typename": [ 1 @@ -9188,7 +9274,7 @@ export default { 3 ], "update": [ - 360 + 363 ], "__typename": [ 1 @@ -9226,7 +9312,7 @@ export default { 3 ], "update": [ - 364 + 367 ], "__typename": [ 1 @@ -9302,7 +9388,7 @@ export default { 3 ], "update": [ - 369 + 372 ], "__typename": [ 1 @@ -9366,10 +9452,10 @@ export default { 3 ], "groups": [ - 374 + 377 ], "fields": [ - 375 + 378 ], "__typename": [ 1 @@ -9389,7 +9475,7 @@ export default { 6 ], "fields": [ - 375 + 378 ], "__typename": [ 1 @@ -9558,7 +9644,7 @@ export default { 3 ], "tabs": [ - 388 + 391 ], "__typename": [ 1 @@ -9581,7 +9667,7 @@ export default { 89 ], "widgets": [ - 389 + 392 ], "__typename": [ 1 @@ -9604,7 +9690,7 @@ export default { 3 ], "gridPosition": [ - 390 + 393 ], "position": [ 15 @@ -9685,7 +9771,7 @@ export default { 3 ], "gridPosition": [ - 390 + 393 ], "position": [ 15 @@ -9711,7 +9797,7 @@ export default { 3 ], "gridPosition": [ - 390 + 393 ], "position": [ 15 @@ -9783,7 +9869,7 @@ export default { 3 ], "update": [ - 397 + 400 ], "__typename": [ 1 @@ -9943,7 +10029,7 @@ export default { 3 ], "update": [ - 402 + 405 ], "__typename": [ 1 @@ -9962,7 +10048,7 @@ export default { }, "CreateOneObjectInput": { "object": [ - 404 + 407 ], "__typename": [ 1 @@ -10022,7 +10108,7 @@ export default { }, "UpdateOneObjectInput": { "update": [ - 407 + 410 ], "id": [ 3 @@ -10077,7 +10163,7 @@ export default { }, "CreateOneIndexInput": { "index": [ - 409 + 412 ], "__typename": [ 1 @@ -10088,7 +10174,7 @@ export default { 3 ], "fields": [ - 410 + 413 ], "indexType": [ 48 @@ -10235,7 +10321,7 @@ export default { }, "UpdateRoleInput": { "update": [ - 416 + 419 ], "id": [ 3 @@ -10290,7 +10376,7 @@ export default { 3 ], "objectPermissions": [ - 418 + 421 ], "__typename": [ 1 @@ -10332,7 +10418,7 @@ export default { 3 ], "fieldPermissions": [ - 421 + 424 ], "__typename": [ 1 @@ -10363,10 +10449,10 @@ export default { 3 ], "predicates": [ - 423 + 426 ], "predicateGroups": [ - 424 + 427 ], "__typename": [ 1 @@ -10424,6 +10510,17 @@ export default { 1 ] }, + "RunAgentInput": { + "agentUniversalIdentifier": [ + 1 + ], + "prompt": [ + 1 + ], + "__typename": [ + 1 + ] + }, "CreateWebhookInput": { "id": [ 3 @@ -10449,7 +10546,7 @@ export default { 3 ], "update": [ - 427 + 431 ], "__typename": [ 1 @@ -10474,7 +10571,7 @@ export default { }, "CreateOneFieldMetadataInput": { "field": [ - 429 + 433 ], "__typename": [ 1 @@ -10547,7 +10644,7 @@ export default { 3 ], "update": [ - 431 + 435 ], "__typename": [ 1 @@ -10639,7 +10736,7 @@ export default { 3 ], "update": [ - 435 + 439 ], "__typename": [ 1 @@ -10683,7 +10780,7 @@ export default { 3 ], "update": [ - 439 + 443 ], "__typename": [ 1 @@ -10702,7 +10799,7 @@ export default { 3 ], "update": [ - 439 + 443 ], "__typename": [ 1 @@ -10713,7 +10810,7 @@ export default { 3 ], "update": [ - 442 + 446 ], "__typename": [ 1 @@ -10721,16 +10818,16 @@ export default { }, "UpdateMessageChannelInputUpdates": { "visibility": [ - 309 + 311 ], "isContactAutoCreationEnabled": [ 6 ], "contactAutoCreationPolicy": [ - 311 + 313 ], "messageFolderImportPolicy": [ - 312 + 314 ], "isSyncEnabled": [ 6 @@ -10758,7 +10855,7 @@ export default { 3 ], "update": [ - 445 + 449 ], "__typename": [ 1 @@ -10766,13 +10863,13 @@ export default { }, "UpdateCalendarChannelInputUpdates": { "visibility": [ - 306 + 308 ], "isContactAutoCreationEnabled": [ 6 ], "contactAutoCreationPolicy": [ - 307 + 309 ], "isSyncEnabled": [ 6 @@ -10874,7 +10971,7 @@ export default { 1 ], "update": [ - 452 + 456 ], "__typename": [ 1 @@ -10922,7 +11019,7 @@ export default { 1 ], "update": [ - 455 + 459 ], "__typename": [ 1 @@ -11040,7 +11137,7 @@ export default { }, "WorkspaceMigrationInput": { "actions": [ - 460 + 464 ], "__typename": [ 1 @@ -11048,10 +11145,10 @@ export default { }, "WorkspaceMigrationDeleteActionInput": { "type": [ - 461 + 465 ], "metadataName": [ - 320 + 322 ], "universalIdentifier": [ 1 @@ -11143,7 +11240,7 @@ export default { 1 ], "files": [ - 467 + 471 ], "__typename": [ 1 @@ -11162,13 +11259,13 @@ export default { }, "EmailAccountConnectionParameters": { "IMAP": [ - 469 + 473 ], "SMTP": [ - 469 + 473 ], "CALDAV": [ - 469 + 473 ], "__typename": [ 1 @@ -11207,7 +11304,7 @@ export default { }, "CreateOneAppTokenInput": { "appToken": [ - 472 + 476 ], "__typename": [ 1 @@ -11233,16 +11330,16 @@ export default { } ], "logicFunctionLogs": [ - 235, + 237, { "input": [ - 475, + 479, "LogicFunctionLogsInput!" ] } ], "onAgentChatEvent": [ - 299, + 301, { "threadId": [ 3, diff --git a/packages/twenty-docs/developers/extend/apps/config/roles.mdx b/packages/twenty-docs/developers/extend/apps/config/roles.mdx index 811589ef64..a780600a7e 100644 --- a/packages/twenty-docs/developers/extend/apps/config/roles.mdx +++ b/packages/twenty-docs/developers/extend/apps/config/roles.mdx @@ -9,8 +9,8 @@ A **role** is a permission set: which objects an app can read or write, which fi ```ts src/roles/restricted-company-role.ts import { defineRole, - PermissionFlag, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, + SystemPermissionFlag, } from 'twenty-sdk/define'; export default defineRole({ @@ -46,7 +46,7 @@ export default defineRole({ canUpdateFieldValue: false, }, ], - permissionFlags: [PermissionFlag.APPLICATIONS], + permissionFlagUniversalIdentifiers: [SystemPermissionFlag.APPLICATIONS], }); ``` @@ -55,7 +55,7 @@ export default defineRole({ When you scaffold a new app, the CLI creates a default role file declared with `defineApplicationRole()`: ```ts src/roles/default-role.ts -import { defineApplicationRole, PermissionFlag } from 'twenty-sdk/define'; +import { defineApplicationRole } from 'twenty-sdk/define'; export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = 'b648f87b-1d26-4961-b974-0908fd991061'; @@ -74,7 +74,7 @@ export default defineApplicationRole({ canBeAssignedToApiKeys: false, objectPermissions: [], fieldPermissions: [], - permissionFlags: [], + permissionFlagUniversalIdentifiers: [], }); ``` @@ -90,5 +90,5 @@ Notes: - Start from the scaffolded role, then progressively restrict it — the default grants broad read access, which is rarely what you want in production. - Replace `objectPermissions` and `fieldPermissions` with the exact objects and fields your functions actually need. -- `permissionFlags` control access to platform-level capabilities. Keep them minimal. +- `permissionFlagUniversalIdentifiers` control access to platform-level capabilities. Keep them minimal. - See a working example: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts). diff --git a/packages/twenty-docs/developers/extend/apps/logic/skills-and-agents.mdx b/packages/twenty-docs/developers/extend/apps/logic/skills-and-agents.mdx index cd0a969b77..b6e7a0b781 100644 --- a/packages/twenty-docs/developers/extend/apps/logic/skills-and-agents.mdx +++ b/packages/twenty-docs/developers/extend/apps/logic/skills-and-agents.mdx @@ -64,6 +64,86 @@ Key points: - `description` (optional) provides context about what the agent does. - `icon` (optional) sets the icon displayed in the UI. - `modelId` (optional) overrides the default AI model used by the agent. +- `responseFormat` (optional) controls the shape of the agent's output. Defaults to `{ type: 'text' }` for free-form text. Use `{ type: 'json', schema }` to force structured JSON output. + +By default an agent returns free-form text. To get structured output, set `responseFormat` to `{ type: 'json' }` and provide a `schema`: + +```ts src/agents/structured-agent.ts +import { defineAgent } from 'twenty-sdk/define'; + +export default defineAgent({ + universalIdentifier: 'c4d5e6f7-a8b9-0123-cdef-456789012345', + name: 'lead-scorer', + label: 'Lead Scorer', + prompt: 'Score the lead and explain your reasoning.', + responseFormat: { + type: 'json', + schema: { + type: 'object', + properties: { + score: { type: 'number', description: 'Lead score from 0 to 100' }, + summary: { type: 'string', description: 'Short reasoning for the score' }, + }, + required: ['score', 'summary'], + additionalProperties: false, + }, + }, +}); +``` + +Schema notes: +- The schema is a flat object: each property's `type` must be a primitive (`string`, `number`, or `boolean`). Nested objects and arrays are not supported. +- `description` (optional) on each property guides the model on what to put there. +- `required` (optional) lists the properties the model must always return. +- `additionalProperties: false` (optional) forbids any property not declared in `properties`. + + + + +`runAgent()` lets a logic function run one of your app's agents (with its +skills and tools). Identify the agent by the `universalIdentifier` you passed +to `defineAgent()`: + +```ts src/logic-functions/run-enricher.ts +import { runAgent } from 'twenty-sdk/logic-function'; + +const { result, error, success } = await runAgent({ + agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123', + prompt: 'Enrich House Ad : fill empty fields from its listing URL.', +}); +``` + +Key points: +- The agent runs **synchronously** and can read/update records itself via its + own tools — `runAgent()` resolves once the run completes. +- An app can only run its own agents. +- The app's [default role](/developers/extend/apps/config/roles) must grant the + `AI` permission flag — add `SystemPermissionFlag.AI` to its + `permissionFlagUniversalIdentifiers` (or set `canAccessAllTools: true`). + Without it, `runAgent()` fails with a permission error. +- Set a generous `timeoutSeconds` on the logic function — agent runs can take + several seconds. +- `success` is `true` and `result` is non-null when the run completes; on + failure `success` is `false`, `result` is `null`, and `error` holds the + reason (for example, when the workspace ran out of AI credits mid-run). + +```ts src/roles/default-role.ts +import { defineApplicationRole, SystemPermissionFlag } from 'twenty-sdk/define'; + +export default defineApplicationRole({ + universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061', + label: 'Default function role', + // runAgent() requires the AI permission flag on the app's default role. + permissionFlagUniversalIdentifiers: [SystemPermissionFlag.AI], +}); +``` + + + **Avoid loops:** if you call `runAgent()` from a `*.updated` database-event + trigger and the agent updates the same record, scope the trigger with + `updatedFields` to a field the agent never writes (e.g. the source URL), or + guard on whether any target field is still empty before calling `runAgent()`. + diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f01bc182b2..6f8c442351 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -256,6 +256,19 @@ export type ApiKeyToken = { token: Scalars['String']; }; +export type AppConnection = { + __typename?: 'AppConnection'; + accessToken: Scalars['String']; + authFailedAt?: Maybe; + handle: Scalars['String']; + id: Scalars['ID']; + name: Scalars['String']; + providerName: Scalars['String']; + scopes: Array; + userWorkspaceId: Scalars['String']; + visibility: Scalars['String']; +}; + export type AppToken = { __typename?: 'AppToken'; createdAt: Scalars['DateTime']; @@ -2116,6 +2129,12 @@ export type LineChartSeries = { label: Scalars['String']; }; +export type ListAppConnectionsInput = { + providerName?: InputMaybe; + userWorkspaceId?: InputMaybe; + visibility?: InputMaybe; +}; + export type Location = { __typename?: 'Location'; lat?: Maybe; @@ -2495,6 +2514,7 @@ export type Mutation = { resetPageLayoutWidgetToDefault: PageLayoutWidget; revokeApiKey?: Maybe; rotateApplicationRegistrationClientSecret: RotateClientSecret; + runAgent: RunAgentResult; runEvaluationInput: AgentTurn; runWorkspaceMigration: Scalars['Boolean']; saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess; @@ -3194,6 +3214,11 @@ export type MutationRotateApplicationRegistrationClientSecretArgs = { }; +export type MutationRunAgentArgs = { + input: RunAgentInput; +}; + + export type MutationRunEvaluationInputArgs = { agentId: Scalars['UUID']; input: Scalars['String']; @@ -4114,6 +4139,8 @@ export type Query = { agentTurns: Array; apiKey?: Maybe; apiKeys: Array; + appConnection: AppConnection; + appConnections: Array; applicationConnectionProviders: Array; applicationRegistrationTarballUrl?: Maybe; barChartData: BarChartData; @@ -4223,6 +4250,16 @@ export type QueryApiKeyArgs = { }; +export type QueryAppConnectionArgs = { + id: Scalars['ID']; +}; + + +export type QueryAppConnectionsArgs = { + filter?: InputMaybe; +}; + + export type QueryApplicationConnectionProvidersArgs = { applicationId: Scalars['UUID']; }; @@ -4735,6 +4772,18 @@ export enum RowLevelPermissionPredicateOperand { VECTOR_SEARCH = 'VECTOR_SEARCH' } +export type RunAgentInput = { + agentUniversalIdentifier: Scalars['String']; + prompt: Scalars['String']; +}; + +export type RunAgentResult = { + __typename?: 'RunAgentResult'; + error?: Maybe; + result?: Maybe; + success: Scalars['Boolean']; +}; + export type SsoConnection = { __typename?: 'SSOConnection'; id: Scalars['UUID']; diff --git a/packages/twenty-front/src/modules/marketplace/utils/buildPermissionSummaryFromRoleManifest.ts b/packages/twenty-front/src/modules/marketplace/utils/buildPermissionSummaryFromRoleManifest.ts index 7046dd9fdb..2a37018452 100644 --- a/packages/twenty-front/src/modules/marketplace/utils/buildPermissionSummaryFromRoleManifest.ts +++ b/packages/twenty-front/src/modules/marketplace/utils/buildPermissionSummaryFromRoleManifest.ts @@ -9,6 +9,7 @@ import { IconKey, IconSettings, IconSettingsAutomation, + IconSparkles, IconTool, IconUsers, } from 'twenty-ui/display'; @@ -114,6 +115,10 @@ export const buildPermissionSummaryFromRoleManifest = ( label: 'Manage API keys and webhooks', Icon: IconCode, }, + [SystemPermissionFlag.AI]: { + label: 'Run AI agents', + Icon: IconSparkles, + }, }; for (const flag of otherFlags) { diff --git a/packages/twenty-sdk/src/cli/utilities/entity/entity-agent-template.ts b/packages/twenty-sdk/src/cli/utilities/entity/entity-agent-template.ts index 5197ba8a3e..af6c2c827b 100644 --- a/packages/twenty-sdk/src/cli/utilities/entity/entity-agent-template.ts +++ b/packages/twenty-sdk/src/cli/utilities/entity/entity-agent-template.ts @@ -21,6 +21,7 @@ export default defineAgent({ label: '${name}', description: 'Add a description for your agent', prompt: 'Add the agent system prompt here', + responseFormat: {type: 'text'}, }); `; }; diff --git a/packages/twenty-sdk/src/sdk/define/agents/define-agent.ts b/packages/twenty-sdk/src/sdk/define/agents/define-agent.ts index 006988a209..c72f988702 100644 --- a/packages/twenty-sdk/src/sdk/define/agents/define-agent.ts +++ b/packages/twenty-sdk/src/sdk/define/agents/define-agent.ts @@ -4,6 +4,7 @@ import { type AgentManifest } from 'twenty-shared/application'; export const defineAgent: DefineEntity = (config) => { const errors: string[] = []; + const warnings: string[] = []; if (!config.universalIdentifier) { errors.push('Agent must have a universalIdentifier'); @@ -21,5 +22,11 @@ export const defineAgent: DefineEntity = (config) => { errors.push('Agent must have a prompt'); } - return createValidationResult({ config, errors }); + if (!config.responseFormat) { + warnings.push( + `Agent '${config.name}' has no responseFormat, it will default to { type: 'text' }. Set it explicitly to control the agent's output shape.`, + ); + } + + return createValidationResult({ config, errors, warnings }); }; diff --git a/packages/twenty-sdk/src/sdk/logic-function/agents/__tests__/run-agent.spec.ts b/packages/twenty-sdk/src/sdk/logic-function/agents/__tests__/run-agent.spec.ts new file mode 100644 index 0000000000..edaaf800a3 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-function/agents/__tests__/run-agent.spec.ts @@ -0,0 +1,103 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, + vi, + type MockInstance, +} from 'vitest'; + +import { runAgent } from '@/sdk/logic-function/agents/run-agent'; + +describe('runAgent', () => { + let fetchSpy: MockInstance; + + beforeEach(() => { + process.env.TWENTY_API_URL = 'https://api.test'; + process.env.TWENTY_APP_ACCESS_TOKEN = 'app-token'; + fetchSpy = vi.spyOn(globalThis, 'fetch'); + }); + + afterEach(() => { + delete process.env.TWENTY_API_URL; + delete process.env.TWENTY_APP_ACCESS_TOKEN; + fetchSpy.mockRestore(); + }); + + it('POSTs the runAgent mutation to /metadata and returns the result', async () => { + const payload = { + result: { response: 'done' }, + error: null, + success: true, + }; + + fetchSpy.mockResolvedValue( + new Response(JSON.stringify({ data: { runAgent: payload } }), { + status: 200, + }), + ); + + const result = await runAgent({ + agentUniversalIdentifier: 'agent-uid', + prompt: 'Enrich record 123', + }); + + expect(result).toEqual(payload); + + const [url, requestInit] = fetchSpy.mock.calls[0]; + + expect(url).toBe('https://api.test/metadata'); + expect(requestInit).toEqual( + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ + Authorization: 'Bearer app-token', + }), + }), + ); + + const sentBody = JSON.parse(requestInit?.body as string); + + expect(sentBody.query).toContain('runAgent(input: $input)'); + expect(sentBody.variables).toEqual({ + input: { + agentUniversalIdentifier: 'agent-uid', + prompt: 'Enrich record 123', + }, + }); + }); + + it('surfaces GraphQL errors as a regular Error', async () => { + fetchSpy.mockResolvedValue( + new Response( + JSON.stringify({ errors: [{ message: 'Agent not found' }] }), + { status: 200 }, + ), + ); + + await expect( + runAgent({ agentUniversalIdentifier: 'a', prompt: 'p' }), + ).rejects.toThrow(/Agent not found/); + }); + + it('surfaces non-2xx HTTP responses as a regular Error', async () => { + fetchSpy.mockResolvedValue( + new Response('boom', { status: 500, statusText: 'Server Error' }), + ); + + await expect( + runAgent({ agentUniversalIdentifier: 'a', prompt: 'p' }), + ).rejects.toThrow(/HTTP 500/); + }); + + it('throws when the runtime env vars are missing', async () => { + delete process.env.TWENTY_API_URL; + + await expect( + runAgent({ agentUniversalIdentifier: 'a', prompt: 'p' }), + ).rejects.toThrow(/requires the app runtime env vars/); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/twenty-sdk/src/sdk/logic-function/agents/run-agent.ts b/packages/twenty-sdk/src/sdk/logic-function/agents/run-agent.ts new file mode 100644 index 0000000000..84308ef7bf --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-function/agents/run-agent.ts @@ -0,0 +1,31 @@ +import { + type RunAgentInput, + type RunAgentResult, +} from 'twenty-shared/application'; + +import { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util'; + +const RUN_AGENT_MUTATION = ` + mutation RunAgent($input: RunAgentInput!) { + runAgent(input: $input) { + result + error + success + } + } +`; + +export const runAgent = async ( + input: RunAgentInput, +): Promise => { + const { runAgent: result } = await postGraphqlRequest< + { input: RunAgentInput }, + { runAgent: RunAgentResult } + >({ + query: RUN_AGENT_MUTATION, + variables: { input }, + caller: 'runAgent', + }); + + return result; +}; diff --git a/packages/twenty-sdk/src/sdk/logic-function/connections/__tests__/get-connection.spec.ts b/packages/twenty-sdk/src/sdk/logic-function/connections/__tests__/get-connection.spec.ts index 7174f2a80b..99440f9365 100644 --- a/packages/twenty-sdk/src/sdk/logic-function/connections/__tests__/get-connection.spec.ts +++ b/packages/twenty-sdk/src/sdk/logic-function/connections/__tests__/get-connection.spec.ts @@ -42,37 +42,48 @@ describe('getConnection', () => { fetchSpy.mockRestore(); }); - it('POSTs the id to /apps/connections/get and returns the response', async () => { + it('queries appConnection by id over GraphQL and returns the response', async () => { const connection = buildConnection({ id: 'persisted' }); fetchSpy.mockResolvedValue( - new Response(JSON.stringify(connection), { status: 200 }), + new Response(JSON.stringify({ data: { appConnection: connection } }), { + status: 200, + }), ); const result = await getConnection('persisted'); expect(result).toEqual(connection); - expect(fetchSpy).toHaveBeenCalledWith( - 'https://api.test/apps/connections/get', + + const [url, requestInit] = fetchSpy.mock.calls[0]; + + expect(url).toBe('https://api.test/metadata'); + expect(requestInit).toEqual( expect.objectContaining({ method: 'POST', - body: JSON.stringify({ id: 'persisted' }), headers: expect.objectContaining({ Authorization: 'Bearer app-token', }), }), ); + + const sentBody = JSON.parse(requestInit?.body as string); + + expect(sentBody.query).toContain('appConnection(id: $id)'); + expect(sentBody.variables).toEqual({ id: 'persisted' }); }); it('throws AppConnectionAuthFailedError when the connection needs reconnect', async () => { fetchSpy.mockResolvedValue( new Response( - JSON.stringify( - buildConnection({ - id: 'broken', - authFailedAt: '2024-01-02T00:00:00.000Z', - }), - ), + JSON.stringify({ + data: { + appConnection: buildConnection({ + id: 'broken', + authFailedAt: '2024-01-02T00:00:00.000Z', + }), + }, + }), { status: 200 }, ), ); diff --git a/packages/twenty-sdk/src/sdk/logic-function/connections/find-connection-for-request.ts b/packages/twenty-sdk/src/sdk/logic-function/connections/find-connection-for-request.ts index fc1c755c7a..a4bfed4246 100644 --- a/packages/twenty-sdk/src/sdk/logic-function/connections/find-connection-for-request.ts +++ b/packages/twenty-sdk/src/sdk/logic-function/connections/find-connection-for-request.ts @@ -1,20 +1,9 @@ import { type AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type'; -// Resolution rule for an HTTP-route handler that wants "the request user's -// connection, or fall back to a workspace-shared one." Pure function — no -// network. Pass it the result of `listConnections` and the trigger event. -// -// The plan-of-record for picking a connection is documented in the v3 plan -// notes; this utility encodes the most common case so handlers don't have -// to repeat the same `find(...) ?? find(...)` chain. -// -// Returns `null` when no candidate exists — caller decides whether that's -// a 4xx for the end user or a hard error. export const findConnectionForRequest = ( connections: AppConnection[], event: { userWorkspaceId: string | null }, ): AppConnection | null => { - // 1. Personal credential of the request user (highest specificity). if (event.userWorkspaceId !== null) { const personal = connections.find( (connection) => @@ -27,7 +16,6 @@ export const findConnectionForRequest = ( } } - // 2. Any workspace-shared credential (team-managed service account). const workspaceShared = connections.find( (connection) => connection.visibility === 'workspace', ); diff --git a/packages/twenty-sdk/src/sdk/logic-function/connections/get-connection.ts b/packages/twenty-sdk/src/sdk/logic-function/connections/get-connection.ts index 010bce0027..d74d2acaee 100644 --- a/packages/twenty-sdk/src/sdk/logic-function/connections/get-connection.ts +++ b/packages/twenty-sdk/src/sdk/logic-function/connections/get-connection.ts @@ -1,25 +1,36 @@ import { AppConnectionAuthFailedError } from '@/sdk/logic-function/connections/errors/app-connection-auth-failed.error'; import { type AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type'; -import { postConnectionsEndpoint } from '@/sdk/logic-function/connections/utils/post-connections-endpoint.util'; +import { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util'; + +const GET_APP_CONNECTION_QUERY = ` + query GetAppConnection($id: ID!) { + appConnection(id: $id) { + id + providerName + name + handle + visibility + userWorkspaceId + accessToken + scopes + authFailedAt + } + } +`; -// Look up a single connection by id. The id is stable across reconnects -// (the row keeps its id when the user clicks "Reconnect"), so apps can -// safely persist it in their own data and call this helper on each -// invocation to retrieve a fresh access token. -// -// Throws `AppConnectionAuthFailedError` if the credential is in a -// permanent-failure state (the user must reconnect from the app's -// settings tab). Throws a regular `Error` for any other failure -// (network, not-found, transient refresh failure). export const getConnection = async (id: string): Promise => { - const connection = await postConnectionsEndpoint< + const { appConnection } = await postGraphqlRequest< { id: string }, - AppConnection - >('get', { id }); + { appConnection: AppConnection } + >({ + query: GET_APP_CONNECTION_QUERY, + variables: { id }, + caller: 'getConnection', + }); - if (connection.authFailedAt !== null) { - throw new AppConnectionAuthFailedError(connection.id); + if (appConnection.authFailedAt !== null) { + throw new AppConnectionAuthFailedError(appConnection.id); } - return connection; + return appConnection; }; diff --git a/packages/twenty-sdk/src/sdk/logic-function/connections/list-connections.ts b/packages/twenty-sdk/src/sdk/logic-function/connections/list-connections.ts index 1ae16be23f..49315bf3df 100644 --- a/packages/twenty-sdk/src/sdk/logic-function/connections/list-connections.ts +++ b/packages/twenty-sdk/src/sdk/logic-function/connections/list-connections.ts @@ -1,26 +1,39 @@ import { type AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type'; -import { postConnectionsEndpoint } from '@/sdk/logic-function/connections/utils/post-connections-endpoint.util'; +import { postGraphqlRequest } from '@/sdk/logic-function/utils/post-graphql-request.util'; + +const LIST_APP_CONNECTIONS_QUERY = ` + query ListAppConnections($filter: ListAppConnectionsInput) { + appConnections(filter: $filter) { + id + providerName + name + handle + visibility + userWorkspaceId + accessToken + scopes + authFailedAt + } + } +`; export type ListConnectionsFilter = { - // Provider name as declared on `defineConnectionProvider({ name })`. providerName?: string; - // Restrict to credentials owned by a specific user. Useful in cron - // triggers when picking a service-account user via app config. userWorkspaceId?: string; - // Restrict by row visibility — 'user' (private) or 'workspace' (shared). visibility?: 'user' | 'workspace'; }; -// Returns every connection owned by the running app, optionally filtered. -// The server refreshes each access token on read, so the returned values -// are usable immediately. When the running execution carries a user -// context (HTTP-route trigger with `isAuthRequired`, tool calls, etc.), -// `scope: 'user'` connections belonging to other users are filtered out -// server-side. Cron and database-event triggers see all connections. export const listConnections = async ( filter: ListConnectionsFilter = {}, -): Promise => - postConnectionsEndpoint( - 'list', - filter, - ); +): Promise => { + const { appConnections } = await postGraphqlRequest< + { filter: ListConnectionsFilter }, + { appConnections: AppConnection[] } + >({ + query: LIST_APP_CONNECTIONS_QUERY, + variables: { filter }, + caller: 'listConnections', + }); + + return appConnections; +}; diff --git a/packages/twenty-sdk/src/sdk/logic-function/connections/utils/post-connections-endpoint.util.ts b/packages/twenty-sdk/src/sdk/logic-function/connections/utils/post-connections-endpoint.util.ts deleted file mode 100644 index 845d9ba012..0000000000 --- a/packages/twenty-sdk/src/sdk/logic-function/connections/utils/post-connections-endpoint.util.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - DEFAULT_API_URL_NAME, - DEFAULT_APP_ACCESS_TOKEN_NAME, -} from 'twenty-shared/application'; - -// Shared transport for `/apps/connections/*` endpoints. Centralises the -// env-var check, auth header, and HTTP error translation so each helper -// stays focused on its own input/output shape. -export const postConnectionsEndpoint = async ( - path: 'list' | 'get', - body: TBody, -): Promise => { - const apiUrl = process.env[DEFAULT_API_URL_NAME]; - const accessToken = process.env[DEFAULT_APP_ACCESS_TOKEN_NAME]; - - if (!apiUrl || !accessToken) { - throw new Error( - `${path === 'list' ? 'listConnections' : 'getConnection'}() requires the app runtime env vars ` + - `${DEFAULT_API_URL_NAME} and ${DEFAULT_APP_ACCESS_TOKEN_NAME}.`, - ); - } - - const response = await fetch(`${apiUrl}/apps/connections/${path}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${accessToken}`, - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - throw new Error( - `${path === 'list' ? 'listConnections' : 'getConnection'}() failed: ` + - `HTTP ${response.status} ${response.statusText}`, - ); - } - - return (await response.json()) as TResponse; -}; diff --git a/packages/twenty-sdk/src/sdk/logic-function/index.ts b/packages/twenty-sdk/src/sdk/logic-function/index.ts index f72aa89fee..b29b72288f 100644 --- a/packages/twenty-sdk/src/sdk/logic-function/index.ts +++ b/packages/twenty-sdk/src/sdk/logic-function/index.ts @@ -44,5 +44,9 @@ export type { ListConnectionsFilter } from '@/sdk/logic-function/connections/lis export { findConnectionForRequest } from '@/sdk/logic-function/connections/find-connection-for-request'; export { AppConnectionAuthFailedError } from '@/sdk/logic-function/connections/errors/app-connection-auth-failed.error'; export type { AppConnection } from '@/sdk/logic-function/connections/types/app-connection.type'; + +export { runAgent } from '@/sdk/logic-function/agents/run-agent'; +export type { RunAgentInput, RunAgentResult } from 'twenty-shared/application'; + export { Response } from '@/sdk/logic-function/response'; export type { ResponseInit } from '@/sdk/logic-function/response'; diff --git a/packages/twenty-sdk/src/sdk/logic-function/utils/post-graphql-request.util.ts b/packages/twenty-sdk/src/sdk/logic-function/utils/post-graphql-request.util.ts new file mode 100644 index 0000000000..34a2072286 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-function/utils/post-graphql-request.util.ts @@ -0,0 +1,56 @@ +import { + DEFAULT_API_URL_NAME, + DEFAULT_APP_ACCESS_TOKEN_NAME, +} from 'twenty-shared/application'; + +export const postGraphqlRequest = async ({ + query, + variables, + caller, +}: { + query: string; + variables: TVariables; + caller: string; +}): Promise => { + const apiUrl = process.env[DEFAULT_API_URL_NAME]; + const accessToken = process.env[DEFAULT_APP_ACCESS_TOKEN_NAME]; + + if (!apiUrl || !accessToken) { + throw new Error( + `${caller}() requires the app runtime env vars ` + + `${DEFAULT_API_URL_NAME} and ${DEFAULT_APP_ACCESS_TOKEN_NAME}.`, + ); + } + + const response = await fetch(`${apiUrl}/metadata`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify({ query, variables }), + }); + + if (!response.ok) { + throw new Error( + `${caller}() failed: HTTP ${response.status} ${response.statusText}`, + ); + } + + const body = (await response.json()) as { + data?: TData; + errors?: { message: string }[]; + }; + + if (body.errors && body.errors.length > 0) { + throw new Error( + `${caller}() failed: ${body.errors.map((error) => error.message).join(', ')}`, + ); + } + + if (!body.data) { + throw new Error(`${caller}() failed: response contained no data.`); + } + + return body.data; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.controller.ts b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.controller.ts index d49100f761..29eb1892f5 100644 --- a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.controller.ts @@ -22,9 +22,10 @@ import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -// On-demand connection lookup for app logic functions. Authenticated via the -// application access token (already injected into the function runtime as -// TWENTY_APP_ACCESS_TOKEN). Apps can only list their own connections. +/** @deprecated Superseded by the `appConnections` / `appConnection` GraphQL + * queries on the metadata schema (ApplicationConnectionsResolver). The SDK + * helpers (`listConnections`, `getConnection`) now call GraphQL. Kept for + * backward compatibility with already-deployed app runtimes. */ @Controller('apps/connections') @UseGuards(JwtAuthGuard, WorkspaceAuthGuard, NoPermissionGuard) @UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })) diff --git a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.module.ts b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.module.ts index 52fb9e6f48..075472748e 100644 --- a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.module.ts +++ b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ConnectionProviderEntity } from 'src/engine/core-modules/application/connection-provider/connection-provider.entity'; import { ApplicationConnectionsController } from 'src/engine/core-modules/application/connection-provider/connections/application-connections.controller'; +import { ApplicationConnectionsResolver } from 'src/engine/core-modules/application/connection-provider/connections/application-connections.resolver'; import { ApplicationConnectionsListService } from 'src/engine/core-modules/application/connection-provider/connections/services/application-connections-list.service'; import { TokenModule } from 'src/engine/core-modules/auth/token/token.module'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @@ -25,7 +26,10 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres RefreshTokensManagerModule, ConnectedAccountTokenEncryptionModule, ], - providers: [ApplicationConnectionsListService], + providers: [ + ApplicationConnectionsListService, + ApplicationConnectionsResolver, + ], controllers: [ApplicationConnectionsController], exports: [ApplicationConnectionsListService], }) diff --git a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.resolver.ts new file mode 100644 index 0000000000..2ffbdaee10 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/application-connections.resolver.ts @@ -0,0 +1,54 @@ +import { UseGuards } from '@nestjs/common'; +import { Args, ID, Query } from '@nestjs/graphql'; + +import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; +import { AppConnectionObjectDto } from 'src/engine/core-modules/application/connection-provider/connections/dtos/app-connection.object'; +import { ListAppConnectionsInput } from 'src/engine/core-modules/application/connection-provider/connections/dtos/list-app-connections.input'; +import { ApplicationConnectionsListService } from 'src/engine/core-modules/application/connection-provider/connections/services/application-connections-list.service'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type'; +import { AuthApplication } from 'src/engine/decorators/auth/auth-application.decorator'; +import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; + +@UseGuards(WorkspaceAuthGuard, NoPermissionGuard) +@MetadataResolver() +export class ApplicationConnectionsResolver { + constructor( + private readonly listService: ApplicationConnectionsListService, + ) {} + + @Query(() => [AppConnectionObjectDto]) + async appConnections( + @AuthApplication() application: FlatApplication, + @AuthWorkspace() workspace: FlatWorkspace, + @AuthUserWorkspaceId({ allowUndefined: true }) + userWorkspaceId: string | undefined, + @Args('filter', { nullable: true }) filter?: ListAppConnectionsInput, + ): Promise { + return this.listService.list({ + applicationId: application.id, + workspaceId: workspace.id, + requestUserWorkspaceId: userWorkspaceId ?? null, + filter: filter ?? {}, + }); + } + + @Query(() => AppConnectionObjectDto) + async appConnection( + @AuthApplication() application: FlatApplication, + @AuthWorkspace() workspace: FlatWorkspace, + @AuthUserWorkspaceId({ allowUndefined: true }) + userWorkspaceId: string | undefined, + @Args('id', { type: () => ID }) id: string, + ): Promise { + return this.listService.getOne({ + applicationId: application.id, + workspaceId: workspace.id, + requestUserWorkspaceId: userWorkspaceId ?? null, + id, + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/dtos/app-connection.object.ts b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/dtos/app-connection.object.ts new file mode 100644 index 0000000000..cbab44db00 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/dtos/app-connection.object.ts @@ -0,0 +1,33 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; + +import { type AppConnection } from 'twenty-shared/application'; + +@ObjectType('AppConnection') +export class AppConnectionObjectDto implements AppConnection { + @Field(() => ID) + id: string; + + @Field() + providerName: string; + + @Field() + name: string; + + @Field() + handle: string; + + @Field(() => String) + visibility: 'user' | 'workspace'; + + @Field() + userWorkspaceId: string; + + @Field() + accessToken: string; + + @Field(() => [String]) + scopes: string[]; + + @Field(() => String, { nullable: true }) + authFailedAt: string | null; +} diff --git a/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/dtos/list-app-connections.input.ts b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/dtos/list-app-connections.input.ts new file mode 100644 index 0000000000..10dfd2c945 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/connection-provider/connections/dtos/list-app-connections.input.ts @@ -0,0 +1,21 @@ +import { Field, InputType } from '@nestjs/graphql'; + +import { IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; + +@InputType('ListAppConnectionsInput') +export class ListAppConnectionsInput { + @IsString() + @IsOptional() + @Field({ nullable: true }) + providerName?: string; + + @IsUUID() + @IsOptional() + @Field({ nullable: true }) + userWorkspaceId?: string; + + @IsIn(['user', 'workspace']) + @IsOptional() + @Field(() => String, { nullable: true }) + visibility?: 'user' | 'workspace'; +} diff --git a/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts index 1ca6978486..6f76df35e1 100644 --- a/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts @@ -22,7 +22,7 @@ export const fromAgentManifestToUniversalFlatAgent = ({ description: agentManifest.description ?? null, prompt: agentManifest.prompt, modelId: (agentManifest.modelId as ModelId) ?? AUTO_SELECT_SMART_MODEL_ID, - responseFormat: { type: 'text' }, + responseFormat: agentManifest.responseFormat ?? { type: 'text' }, modelConfiguration: null, evaluationInputs: [], isCustom: false, diff --git a/packages/twenty-server/src/engine/decorators/auth/auth-application.decorator.ts b/packages/twenty-server/src/engine/decorators/auth/auth-application.decorator.ts new file mode 100644 index 0000000000..63020fe466 --- /dev/null +++ b/packages/twenty-server/src/engine/decorators/auth/auth-application.decorator.ts @@ -0,0 +1,25 @@ +import { + type ExecutionContext, + ForbiddenException, + createParamDecorator, +} from '@nestjs/common'; + +import { getRequest } from 'src/utils/extract-request'; + +interface DecoratorOptions { + allowUndefined?: boolean; +} + +export const AuthApplication = createParamDecorator( + (options: DecoratorOptions | undefined, ctx: ExecutionContext) => { + const request = getRequest(ctx); + + if (!options?.allowUndefined && !request.application) { + throw new ForbiddenException( + 'This endpoint requires an APPLICATION_ACCESS token.', + ); + } + + return request.application; + }, +); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module.ts index e5d576d38a..1b9579b22d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module.ts @@ -1,6 +1,7 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module'; import { FileUrlModule } from 'src/engine/core-modules/file/file-url/file-url.module'; @@ -21,14 +22,17 @@ import { AgentMessagePartEntity } from './entities/agent-message-part.entity'; import { AgentMessageEntity } from './entities/agent-message.entity'; import { AgentTurnEntity } from './entities/agent-turn.entity'; import { AgentMessagePartResolver } from './resolvers/agent-message-part.resolver'; +import { AgentRunResolver } from './resolvers/agent-run.resolver'; import { AgentActorContextService } from './services/agent-actor-context.service'; import { AgentAsyncExecutorService } from './services/agent-async-executor.service'; +import { AgentRunService } from './services/agent-run.service'; @Module({ imports: [ AiBillingModule, AiModelsModule, AiAgentModule, + ApplicationModule, BillingModule, FileUrlModule, WorkspaceDomainsModule, @@ -50,7 +54,10 @@ import { AgentAsyncExecutorService } from './services/agent-async-executor.servi AgentAsyncExecutorService, AgentActorContextService, AgentMessagePartResolver, + AgentRunResolver, + AgentRunService, provideWorkspaceScopedRepository(RoleTargetEntity), + provideWorkspaceScopedRepository(AgentEntity), ], exports: [ AgentAsyncExecutorService, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent-result.dto.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent-result.dto.ts new file mode 100644 index 0000000000..3f673c43b2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent-result.dto.ts @@ -0,0 +1,16 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import GraphQLJSON from 'graphql-type-json'; +import { type RunAgentResult } from 'twenty-shared/application'; + +@ObjectType('RunAgentResult') +export class RunAgentResultDTO implements RunAgentResult { + @Field(() => GraphQLJSON, { nullable: true }) + result: object | null; + + @Field(() => String, { nullable: true }) + error: string | null; + + @Field() + success: boolean; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent.input.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent.input.ts new file mode 100644 index 0000000000..c021020a18 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent.input.ts @@ -0,0 +1,17 @@ +import { Field, InputType } from '@nestjs/graphql'; + +import { IsNotEmpty, IsString } from 'class-validator'; +import { type RunAgentInput } from 'twenty-shared/application'; + +@InputType('RunAgentInput') +export class RunAgentInputDTO implements RunAgentInput { + @IsString() + @IsNotEmpty() + @Field() + agentUniversalIdentifier: string; + + @IsString() + @IsNotEmpty() + @Field() + prompt: string; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/resolvers/agent-run.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/resolvers/agent-run.resolver.ts new file mode 100644 index 0000000000..245b8aaedb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/resolvers/agent-run.resolver.ts @@ -0,0 +1,34 @@ +import { UseGuards } from '@nestjs/common'; +import { Args, Mutation } from '@nestjs/graphql'; + +import { PermissionFlagType } from 'twenty-shared/constants'; + +import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; +import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type'; +import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { RunAgentInputDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent.input'; +import { RunAgentResultDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/run-agent-result.dto'; +import { AgentRunService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-run.service'; + +@UseGuards(WorkspaceAuthGuard, SettingsPermissionGuard(PermissionFlagType.AI)) +@MetadataResolver() +export class AgentRunResolver { + constructor(private readonly agentRunService: AgentRunService) {} + + @Mutation(() => RunAgentResultDTO) + async runAgent( + @Args('input') input: RunAgentInputDTO, + @AuthWorkspace() workspace: FlatWorkspace, + @AuthUserWorkspaceId({ allowUndefined: true }) + userWorkspaceId: string | undefined, + ): Promise { + return this.agentRunService.run({ + workspace, + requestUserWorkspaceId: userWorkspaceId ?? null, + input, + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-run.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-run.service.spec.ts new file mode 100644 index 0000000000..cfc2fe0bb5 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/__tests__/agent-run.service.spec.ts @@ -0,0 +1,145 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { AgentAsyncExecutorService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service'; +import { AgentRunService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-run.service'; +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { getWorkspaceScopedRepositoryToken } from 'src/engine/twenty-orm/workspace-scoped-repository/get-workspace-scoped-repository-token.util'; + +describe('AgentRunService', () => { + let service: AgentRunService; + let agentRepository: { findOne: jest.Mock }; + let applicationService: { findById: jest.Mock }; + let agentAsyncExecutorService: { executeAgent: jest.Mock }; + + const workspace = { id: 'workspace-1' } as FlatWorkspace; + + const agent = { id: 'agent-1', applicationId: 'app-1' } as AgentEntity; + + const input = { + agentUniversalIdentifier: 'agent-uid', + prompt: 'Enrich record 123', + }; + + beforeEach(async () => { + agentRepository = { findOne: jest.fn().mockResolvedValue(agent) }; + applicationService = { + findById: jest.fn().mockResolvedValue({ id: 'app-1' }), + }; + agentAsyncExecutorService = { + executeAgent: jest.fn().mockResolvedValue({ + result: { response: 'done' }, + hasNoMoreAvailableCredits: false, + }), + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AgentRunService, + { + provide: AgentAsyncExecutorService, + useValue: agentAsyncExecutorService, + }, + { + provide: ApplicationService, + useValue: applicationService, + }, + { + provide: getWorkspaceScopedRepositoryToken(AgentEntity), + useValue: agentRepository, + }, + ], + }).compile(); + + service = module.get(AgentRunService); + }); + + it('runs the agent found by its universal identifier and returns a success result', async () => { + const result = await service.run({ + workspace, + requestUserWorkspaceId: 'user-workspace-1', + input, + }); + + expect(agentRepository.findOne).toHaveBeenCalledWith(workspace.id, { + where: { + universalIdentifier: input.agentUniversalIdentifier, + }, + }); + expect(applicationService.findById).toHaveBeenCalledWith( + agent.applicationId, + ); + expect(result).toEqual({ + result: { response: 'done' }, + error: null, + success: true, + }); + }); + + it('builds the application auth context from the agent application', async () => { + await service.run({ + workspace, + requestUserWorkspaceId: 'user-workspace-1', + input, + }); + + expect(agentAsyncExecutorService.executeAgent).toHaveBeenCalledWith( + expect.objectContaining({ + authContext: { + type: 'application', + workspace, + application: { id: 'app-1' }, + }, + }), + ); + }); + + it('returns an error result when the workspace ran out of credits', async () => { + agentAsyncExecutorService.executeAgent.mockResolvedValue({ + result: { response: 'partial' }, + hasNoMoreAvailableCredits: true, + }); + + const result = await service.run({ + workspace, + requestUserWorkspaceId: 'user-workspace-1', + input, + }); + + expect(result).toEqual({ + result: null, + error: 'AI agent stopped: no more available credits.', + success: false, + }); + }); + + it('throws when no agent matches the identifier', async () => { + agentRepository.findOne.mockResolvedValue(null); + + await expect( + service.run({ + workspace, + requestUserWorkspaceId: null, + input, + }), + ).rejects.toThrow(/not found/); + + expect(applicationService.findById).not.toHaveBeenCalled(); + expect(agentAsyncExecutorService.executeAgent).not.toHaveBeenCalled(); + }); + + it("throws when the agent's application cannot be found", async () => { + applicationService.findById.mockResolvedValue(null); + + await expect( + service.run({ + workspace, + requestUserWorkspaceId: null, + input, + }), + ).rejects.toThrow(/not found/); + + expect(agentAsyncExecutorService.executeAgent).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-run.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-run.service.ts new file mode 100644 index 0000000000..88943427f9 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-run.service.ts @@ -0,0 +1,83 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; + +import { + type RunAgentInput, + type RunAgentResult, +} from 'twenty-shared/application'; + +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { UsageOperationType } from 'src/engine/core-modules/usage/enums/usage-operation-type.enum'; +import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type'; +import { AgentAsyncExecutorService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service'; +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator'; +import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository'; + +@Injectable() +export class AgentRunService { + constructor( + private readonly agentAsyncExecutorService: AgentAsyncExecutorService, + private readonly applicationService: ApplicationService, + @InjectWorkspaceScopedRepository(AgentEntity) + private readonly agentRepository: WorkspaceScopedRepository, + ) {} + + async run({ + workspace, + requestUserWorkspaceId, + input, + }: { + workspace: FlatWorkspace; + requestUserWorkspaceId: string | null; + input: RunAgentInput; + }): Promise { + const agent = await this.agentRepository.findOne(workspace.id, { + where: { + universalIdentifier: input.agentUniversalIdentifier, + }, + }); + + if (!agent) { + throw new NotFoundException( + `Agent ${input.agentUniversalIdentifier} not found`, + ); + } + + const application = await this.applicationService.findById( + agent.applicationId, + ); + + if (!application) { + throw new NotFoundException( + `Application ${agent.applicationId} not found for agent ${input.agentUniversalIdentifier}`, + ); + } + + const authContext: WorkspaceAuthContext = { + type: 'application', + workspace, + application, + }; + + const { result, hasNoMoreAvailableCredits } = + await this.agentAsyncExecutorService.executeAgent({ + agent, + userPrompt: input.prompt, + authContext, + workspaceId: workspace.id, + userWorkspaceId: requestUserWorkspaceId, + operationType: UsageOperationType.AI_WORKFLOW_TOKEN, + }); + + if (hasNoMoreAvailableCredits) { + return { + result: null, + error: 'AI agent stopped: no more available credits.', + success: false, + }; + } + + return { result, error: null, success: true }; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts index d0c2a4eb64..0bf547e724 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts @@ -1,12 +1,6 @@ -import { type AgentResponseSchema } from 'twenty-shared/ai'; - -export type AgentResponseFormatType = AgentResponseFormat['type']; - -export type AgentTextResponseFormat = { type: 'text' }; -export type AgentJsonResponseFormat = { - type: 'json'; - schema: AgentResponseSchema; -}; -export type AgentResponseFormat = - | AgentTextResponseFormat - | AgentJsonResponseFormat; +export type { + AgentResponseFormat, + AgentResponseFormatType, + AgentTextResponseFormat, + AgentJsonResponseFormat, +} from 'twenty-shared/ai'; diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts index 83762b3793..c2ec2f13c0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-engine.module.ts @@ -6,6 +6,7 @@ import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent. import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module'; import { AiGenerateTextModule } from 'src/engine/metadata-modules/ai/ai-generate-text/ai-generate-text.module'; import { AiWorkspaceStatsModule } from 'src/engine/metadata-modules/ai/ai-workspace-stats/ai-workspace-stats.module'; +import { ApplicationConnectionsModule } from 'src/engine/core-modules/application/connection-provider/connections/application-connections.module'; import { CalendarChannelMetadataModule } from 'src/engine/metadata-modules/calendar-channel/calendar-channel-metadata.module'; import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module'; import { CommandMenuItemModule } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.module'; @@ -45,6 +46,7 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work AiChatModule, AiGenerateTextModule, AiWorkspaceStatsModule, + ApplicationConnectionsModule, MinimalMetadataModule, ViewModule, WorkspaceMetadataVersionModule, diff --git a/packages/twenty-shared/src/ai/index.ts b/packages/twenty-shared/src/ai/index.ts index 005c37fee4..8a7b6ea4d0 100644 --- a/packages/twenty-shared/src/ai/index.ts +++ b/packages/twenty-shared/src/ai/index.ts @@ -15,6 +15,12 @@ export { DATA_RESIDENCY_KEYS } from './constants/data-residency.const'; export type { NativeAiSdkProviderId } from './constants/native-ai-sdk-provider-ids.const'; export { NATIVE_AI_SDK_PROVIDER_IDS } from './constants/native-ai-sdk-provider-ids.const'; export { ToolCategory } from './constants/tool-category.const'; +export type { + AgentResponseFormatType, + AgentTextResponseFormat, + AgentJsonResponseFormat, + AgentResponseFormat, +} from './types/agent-response-format.type'; export type { AgentResponseFieldType, AgentResponseSchema, diff --git a/packages/twenty-shared/src/ai/types/agent-response-format.type.ts b/packages/twenty-shared/src/ai/types/agent-response-format.type.ts new file mode 100644 index 0000000000..54ba9a303b --- /dev/null +++ b/packages/twenty-shared/src/ai/types/agent-response-format.type.ts @@ -0,0 +1,12 @@ +import { type AgentResponseSchema } from '@/ai/types/agent-response-schema.type'; + +export type AgentResponseFormatType = AgentResponseFormat['type']; + +export type AgentTextResponseFormat = { type: 'text' }; +export type AgentJsonResponseFormat = { + type: 'json'; + schema: AgentResponseSchema; +}; +export type AgentResponseFormat = + | AgentTextResponseFormat + | AgentJsonResponseFormat; diff --git a/packages/twenty-shared/src/application/agentManifestType.ts b/packages/twenty-shared/src/application/agentManifestType.ts index 4084226bee..9d3cef9591 100644 --- a/packages/twenty-shared/src/application/agentManifestType.ts +++ b/packages/twenty-shared/src/application/agentManifestType.ts @@ -1,3 +1,4 @@ +import { type AgentResponseFormat } from '@/ai/types/agent-response-format.type'; import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType'; export type AgentManifest = SyncableEntityOptions & { @@ -7,4 +8,5 @@ export type AgentManifest = SyncableEntityOptions & { description?: string; prompt: string; modelId?: string; + responseFormat?: AgentResponseFormat; }; diff --git a/packages/twenty-shared/src/application/index.ts b/packages/twenty-shared/src/application/index.ts index 47a7081f96..4feb6130e2 100644 --- a/packages/twenty-shared/src/application/index.ts +++ b/packages/twenty-shared/src/application/index.ts @@ -66,6 +66,7 @@ export type { FieldPermissionManifest, RoleManifest, } from './roleManifestType'; +export type { RunAgentInput, RunAgentResult } from './runAgentType'; export type { ServerVariables } from './server-variables.type'; export type { SkillManifest } from './skillManifestType'; export type { StoredOAuthConnectionProviderConfig } from './storedOAuthConnectionProviderConfigType'; diff --git a/packages/twenty-shared/src/application/runAgentType.ts b/packages/twenty-shared/src/application/runAgentType.ts new file mode 100644 index 0000000000..e5f1992cd6 --- /dev/null +++ b/packages/twenty-shared/src/application/runAgentType.ts @@ -0,0 +1,10 @@ +export type RunAgentInput = { + agentUniversalIdentifier: string; + prompt: string; +}; + +export type RunAgentResult = { + result: object | null; + error: string | null; + success: boolean; +};