From e7ebf51e50dac9605844200ff1277275243b58f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 25 Nov 2025 12:10:14 +0100 Subject: [PATCH] Replace agent handoff system with planning-based router (#16003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Overview This PR replaces the dynamic agent handoff system with a more predictable planning-based router that decides upfront how to handle multi-agent coordination. ## Major Changes ### 🔄 Architecture Shift: Handoffs → Planning **Removed:** - `AgentHandoffEntity` and handoff tracking system - `AgentHandoffService` and `AgentHandoffExecutorService` - Dynamic agent-to-agent transfers during execution - Handoff tool generation and description templates **Added:** - `AiRouterService` with two strategies: `simple` (single agent) and `planned` (multi-agent) - `AgentPlanExecutorService` for executing multi-step plans - Plan validation (cycle detection, dependency resolution) - `UnifiedRouterResult` type with discriminated union ### 🤖 New Standard Agents Added two new specialized agents: - **Researcher Agent**: Web search, fact-finding, competitive intelligence - **Code Agent**: TypeScript function generation for serverless workflows ### 🏗️ Router Refactoring (Latest) Split router responsibilities into focused services: - `AiRouterStrategyDeciderService`: Decides simple vs planned strategy - `AiRouterPlanGeneratorService`: Generates and validates execution plans - `AiRouterService`: Coordinates between services (reduced from 426→275 lines) ### ⚙️ Configuration Improvements - Added `outputStrategy` to agent definitions (`direct` vs `synthesize`) - Removed hardcoded special cases for workflow-builder - Added `plannerModel` field to workspace entity - Increased `MAX_STEPS` from 10 to 25 for complex workflows ### 📝 Agent Prompt Refinements Significantly simplified prompts for better clarity: - Workflow Builder: 51→36 lines - Helper: 49→28 lines - Data Manipulator: Enhanced with sorting guidance ### 🔍 Enhanced Debugging - Plan reasoning and step count in data message parts - Router debug info with token usage tracking - Better logging throughout execution pipeline ## Benefits 1. **Simpler Mental Model**: Router decides upfront vs dynamic transfers 2. **Better Predictability**: Users see the plan before execution 3. **Cleaner Architecture**: SRP with focused services 4. **Configuration Over Code**: Agent behavior via config, not hardcoded logic 5. **Plan Validation**: Catches invalid dependencies and cycles ## Migration Notes - Database migration removes `agentHandoff` table - Adds `plannerModel` column to workspace table - No API breaking changes (agent endpoints unchanged) ## Testing - Integration tests updated to remove handoff dependencies - Agent tool test utilities simplified - Plan validation covered by new logic ## Next Steps (Future PRs) - Parallel execution of independent plan steps - Dynamic re-planning based on results - Plan caching for common routing patterns - Error recovery strategies in plan executor --- .../src/generated-metadata/graphql.ts | 236 +------- .../twenty-front/src/generated/graphql.ts | 61 +-- .../ai/components/RoutingDebugDisplay.tsx | 3 +- .../ai/components/RoutingStatusDisplay.tsx | 114 ++-- .../modules/ai/constants/DefaultFastModel.ts | 1 + .../modules/ai/constants/DefaultSmartModel.ts | 1 + .../graphql/mutations/createAgentHandoff.ts | 7 - .../graphql/mutations/removeAgentHandoff.ts | 7 - .../queries/findAgentHandoffTargets.ts | 18 - .../ai/graphql/queries/findAgentHandoffs.ts | 22 - .../src/modules/ai/hooks/useAiModelOptions.ts | 9 +- .../services/__tests__/apollo.factory.test.ts | 4 + .../auth/states/currentWorkspaceState.ts | 3 +- ...olumnDefinitionsFromObjectMetadata.test.ts | 5 +- .../graphql/fragments/userQueryFragment.ts | 3 +- .../components/SettingsAIRouterSettings.tsx | 138 +++-- .../components/SettingsAgentHandoffForm.tsx | 151 ------ .../SettingsAgentHandoffSection.tsx | 64 --- .../components/SettingsAgentHandoffTable.tsx | 186 ------- .../src/testing/mock-data/users.ts | 4 + .../1-10-regenerate-search-vectors.command.ts | 2 +- ...1-1763805200000-RemoveAgentHandoffTable.ts | 36 ++ ...530458-AddFastAndSmartModelsToWorkspace.ts | 30 + .../1764066845539-coreMigrationCheck.ts | 17 + .../ai => api/mcp}/constants/mcp.const.ts | 0 .../__tests__/mcp-core.controller.spec.ts} | 74 +-- .../mcp/controllers/mcp-core.controller.ts} | 10 +- .../mcp}/decorators/string-or-number.ts | 0 .../ai => api/mcp}/dtos/json-rpc.ts | 2 +- .../src/engine/api/mcp/mcp.module.ts | 31 +- .../__tests__/mcp-protocol.service.spec.ts} | 138 +++-- .../api/mcp/services/mcp-metadata.service.ts | 2 +- .../mcp/services/mcp-protocol.service.ts} | 86 +-- .../mcp/services/mcp-tool-executor.service.ts | 75 +++ .../mcp}/utils/wrap-jsonrpc-response.util.ts | 2 +- .../ai/controllers/ai.controller.spec.ts | 190 ------- .../ai/controllers/ai.controller.ts | 99 ---- .../find-records-filters.utils.spec.ts | 120 ---- .../ai/utils/find-records-filters.utils.ts | 112 ---- .../application/application.entity.ts | 2 +- .../application/application.module.ts | 2 +- .../application/dtos/application.dto.ts | 2 +- .../core-modules/billing/billing.module.ts | 8 +- .../billing/services/billing-usage.service.ts | 1 + .../stripe-billing-meter-event.service.ts | 25 +- .../billing/types/billing-dimensions.type.ts | 12 + .../billing/types/billing-usage-event.type.ts | 2 + .../client-config.controller.spec.ts | 2 +- .../client-config/client-config.entity.ts | 2 +- .../services/client-config.service.spec.ts | 2 +- .../services/client-config.service.ts | 57 +- .../engine/core-modules/core-engine.module.ts | 8 +- ...ted-columns-from-restricted-fields.util.ts | 4 +- .../record-properties.zod-schema.ts | 18 +- .../search/services/search.service.ts | 2 +- .../workspace/dtos/update-workspace-input.ts | 7 +- .../workspace/services/workspace.service.ts | 3 +- .../workspace/workspace.entity.ts | 29 +- .../workspace/workspace.module.ts | 6 +- .../workspace/workspace.resolver.ts | 18 +- .../agent/agent-handoff-executor.service.ts | 144 ----- .../agent/agent-handoff-tool.service.ts | 65 --- .../agent/agent-handoff.entity.ts | 71 --- .../agent/agent-handoff.service.ts | 158 ------ .../metadata-modules/agent/agent.service.ts | 165 ------ .../agent-handoff-description.const.ts | 2 - .../constants/agent-handoff-schema.const.ts | 99 ---- .../constants/agent-system-prompts.const.ts | 71 --- .../agent/dtos/agent-handoff.dto.ts | 17 - .../agent/dtos/create-agent-handoff.input.ts | 15 - .../agent/dtos/remove-agent-handoff.input.ts | 12 - .../agent-role.service.spec.ts | 16 +- .../ai-agent-role.module.ts} | 10 +- .../ai-agent-role.service.ts} | 6 +- .../{agent => ai-agent}/agent.exception.ts | 1 - .../{agent => ai-agent}/agent.resolver.ts | 68 +-- .../ai-agent/agent.service.ts | 257 +++++++++ .../ai-agent.module.ts} | 74 +-- .../constants/agent-config.const.ts | 2 +- .../constants/agent-system-prompts.const.ts | 110 ++++ .../dtos/agent-id.input.ts | 0 .../{agent => ai-agent}/dtos/agent.dto.ts | 4 +- .../dtos/create-agent.input.ts | 4 +- .../dtos/update-agent.input.ts | 4 +- .../entities}/agent.entity.ts | 20 +- .../services/agent-actor-context.service.ts | 2 +- .../services}/agent-execution.service.ts | 62 +-- .../services}/agent-model-config.service.ts | 9 +- .../services/agent-plan-executor.service.ts | 248 +++++++++ .../agent-title-generation.service.ts | 4 +- .../services}/agent-tool-generator.service.ts | 92 +++- .../types/agent-response-format.type.ts | 0 .../types/modelConfiguration.ts | 0 ...ordIdsByObjectMetadataNameSingular.type.ts | 0 .../is-workflow-related-object.util.spec.ts | 2 +- .../utils/is-workflow-related-object.util.ts | 0 .../utils/is-workflow-run-object.util.ts | 0 .../utils/repair-tool-call.util.ts | 2 +- .../ai-billing/ai-billing.module.ts | 12 + .../constants/dollar-to-credit-multiplier.ts | 0 .../__tests__/ai-billing.service.spec.ts | 10 +- .../services/ai-billing.service.ts | 16 +- .../convert-cents-to-billing-credits.util.ts | 2 +- .../ai-chat/ai-chat.module.ts | 63 +++ .../controllers}/agent-chat.controller.ts | 7 +- .../dtos/agent-chat-message-part.dto.ts | 0 .../dtos/agent-chat-message.dto.ts | 0 .../dtos/agent-chat-thread.dto.ts | 0 .../agent-chat-message-part.entity.ts | 0 .../entities}/agent-chat-message.entity.ts | 2 +- .../entities}/agent-chat-thread.entity.ts | 2 +- .../resolvers}/agent-chat.resolver.ts | 7 +- .../services}/agent-chat.service.ts | 25 +- .../services}/agent-streaming.service.ts | 140 ++++- .../utils/mapUIMessagePartsToDBParts.ts | 2 +- .../ai-models/ai-models.module.ts | 11 + .../constants/ai-models.const.spec.ts | 20 +- .../ai-models}/constants/ai-models.const.ts | 6 +- .../constants/ai-telemetry.const.ts | 0 .../constants/dollar-to-credit-multiplier.ts | 2 + .../services/ai-model-registry.service.ts | 15 +- .../ai-models}/services/ai.service.ts | 4 +- .../ai-router/ai-router.module.ts | 17 +- .../ai-router/ai-router.service.ts | 511 ++++++++++-------- .../ai-router-plan-generator.service.ts | 192 +++++++ .../ai-router-strategy-decider.service.ts | 198 +++++++ .../types/router-result.interface.ts | 39 ++ .../ai-tools/ai-tools.module.ts} | 33 +- .../__tests__/tool-adapter.service.spec.ts | 2 +- .../services/__tests__/tool.service.spec.ts | 2 +- .../services/tool-adapter.service.ts | 0 .../ai-tools}/services/tool.service.ts | 2 +- .../flat-agent/types/flat-agent.type.ts | 4 +- ...ansform-agent-entity-to-flat-agent.util.ts | 2 +- .../metadata-engine.module.ts | 9 +- .../metadata-modules/role/dtos/role.dto.ts | 2 +- .../metadata-modules/role/role.module.ts | 4 +- .../metadata-modules/role/role.resolver.ts | 6 +- .../search-vector-field.constants.ts | 0 .../twenty-orm/custom.workspace-entity.ts | 2 +- .../workspace-manager.service.spec.ts | 2 +- .../dev-seeder/core/utils/seed-agents.util.ts | 2 +- .../workspace-manager.module.ts | 4 +- .../factories/standard-agent.factory.ts | 2 +- .../services/workspace-sync-agent.service.ts | 2 +- .../agents/data-manipulator-agent.ts | 73 +-- .../standard-agents/agents/helper-agent.ts | 61 +-- .../agents/researcher-agent.ts | 42 ++ .../agents/workflow-builder-agent.ts | 53 +- .../agents/workflow-creation-agent.ts | 51 -- .../standard-agents/index.ts | 3 + .../standard-agent-definition.interface.ts | 3 + .../workspace-sync-metadata.module.ts | 4 +- .../company.workspace-entity.ts | 2 +- .../dashboard.workspace-entity.ts | 2 +- .../standard-objects/note.workspace-entity.ts | 2 +- .../opportunity.workspace-entity.ts | 2 +- .../person.workspace-entity.ts | 2 +- .../standard-objects/task.workspace-entity.ts | 2 +- .../workflow-run.workspace-entity.ts | 2 +- .../workflow-version.workspace-entity.ts | 2 +- .../workflow.workspace-entity.ts | 2 +- .../workflow-schema/workflow-schema.module.ts | 2 +- .../workflow-schema.workspace-service.ts | 2 +- ...-step-operations.workspace-service.spec.ts | 2 +- ...rsion-step-operations.workspace-service.ts | 5 +- .../workflow-version-step.module.ts | 2 +- .../ai-agent/ai-agent-action.module.ts | 10 +- .../ai-agent/ai-agent.workflow-action.ts | 10 +- .../services/ai-agent-executor.service.ts | 20 +- ...orkflow-executor.workspace-service.spec.ts | 13 +- .../workflow-executor.workspace-service.ts | 9 +- .../workspace-member.workspace-entity.ts | 2 +- .../suites/agent/agent.integration-spec.ts | 43 +- ...and-workspace-creation.integration-spec.ts | 1 - .../agent/utils/agent-tool-test-utils.ts | 28 +- .../src/ai/types/DataMessagePart.ts | 11 +- 177 files changed, 2720 insertions(+), 3222 deletions(-) create mode 100644 packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts create mode 100644 packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts delete mode 100644 packages/twenty-front/src/modules/ai/graphql/mutations/createAgentHandoff.ts delete mode 100644 packages/twenty-front/src/modules/ai/graphql/mutations/removeAgentHandoff.ts delete mode 100644 packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffTargets.ts delete mode 100644 packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffs.ts delete mode 100644 packages/twenty-front/src/pages/settings/ai/components/SettingsAgentHandoffForm.tsx delete mode 100644 packages/twenty-front/src/pages/settings/ai/components/SettingsAgentHandoffSection.tsx delete mode 100644 packages/twenty-front/src/pages/settings/ai/components/SettingsAgentHandoffTable.tsx create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1763805513241-1763805200000-RemoveAgentHandoffTable.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1763997530458-AddFastAndSmartModelsToWorkspace.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1764066845539-coreMigrationCheck.ts rename packages/twenty-server/src/engine/{core-modules/ai => api/mcp}/constants/mcp.const.ts (100%) rename packages/twenty-server/src/engine/{core-modules/ai/controllers/mcp.controller.spec.ts => api/mcp/controllers/__tests__/mcp-core.controller.spec.ts} (65%) rename packages/twenty-server/src/engine/{core-modules/ai/controllers/mcp.controller.ts => api/mcp/controllers/mcp-core.controller.ts} (80%) rename packages/twenty-server/src/engine/{core-modules/ai => api/mcp}/decorators/string-or-number.ts (100%) rename packages/twenty-server/src/engine/{core-modules/ai => api/mcp}/dtos/json-rpc.ts (84%) rename packages/twenty-server/src/engine/{core-modules/ai/services/__tests__/mcp.service.spec.ts => api/mcp/services/__tests__/mcp-protocol.service.spec.ts} (85%) rename packages/twenty-server/src/engine/{core-modules/ai/services/mcp.service.ts => api/mcp/services/mcp-protocol.service.ts} (65%) create mode 100644 packages/twenty-server/src/engine/api/mcp/services/mcp-tool-executor.service.ts rename packages/twenty-server/src/engine/{core-modules/ai => api/mcp}/utils/wrap-jsonrpc-response.util.ts (87%) delete mode 100644 packages/twenty-server/src/engine/core-modules/ai/controllers/ai.controller.spec.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/ai/controllers/ai.controller.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/ai/utils/__tests__/find-records-filters.utils.spec.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/ai/utils/find-records-filters.utils.ts create mode 100644 packages/twenty-server/src/engine/core-modules/billing/types/billing-dimensions.type.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/agent-handoff-executor.service.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/agent-handoff-tool.service.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/agent-handoff.entity.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/agent-handoff.service.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/agent.service.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-handoff-description.const.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-handoff-schema.const.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/dtos/agent-handoff.dto.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/dtos/create-agent-handoff.input.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/dtos/remove-agent-handoff.input.ts rename packages/twenty-server/src/engine/metadata-modules/{agent-role => ai-agent-role}/agent-role.service.spec.ts (95%) rename packages/twenty-server/src/engine/metadata-modules/{agent-role/agent-role.module.ts => ai-agent-role/ai-agent-role.module.ts} (58%) rename packages/twenty-server/src/engine/metadata-modules/{agent-role/agent-role.service.ts => ai-agent-role/ai-agent-role.service.ts} (95%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/agent.exception.ts (90%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/agent.resolver.ts (55%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-agent/agent.service.ts rename packages/twenty-server/src/engine/metadata-modules/{agent/agent.module.ts => ai-agent/ai-agent.module.ts} (54%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/constants/agent-config.const.ts (79%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-agent/constants/agent-system-prompts.const.ts rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/dtos/agent-id.input.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/dtos/agent.dto.ts (91%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/dtos/create-agent.input.ts (90%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/dtos/update-agent.input.ts (91%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent/entities}/agent.entity.ts (78%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/services/agent-actor-context.service.ts (97%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent/services}/agent-execution.service.ts (84%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent/services}/agent-model-config.service.ts (84%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-agent/services/agent-plan-executor.service.ts rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent/services}/agent-title-generation.service.ts (88%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent/services}/agent-tool-generator.service.ts (56%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/types/agent-response-format.type.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/types/modelConfiguration.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/types/recordIdsByObjectMetadataNameSingular.type.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/utils/__tests__/is-workflow-related-object.util.spec.ts (95%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/utils/is-workflow-related-object.util.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/utils/is-workflow-run-object.util.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-agent}/utils/repair-tool-call.util.ts (94%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-billing/ai-billing.module.ts rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-billing}/constants/dollar-to-credit-multiplier.ts (100%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-billing}/services/__tests__/ai-billing.service.spec.ts (88%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-billing}/services/ai-billing.service.ts (76%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-billing}/utils/convert-cents-to-billing-credits.util.ts (74%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-chat/ai-chat.module.ts rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/controllers}/agent-chat.controller.ts (86%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat}/dtos/agent-chat-message-part.dto.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat}/dtos/agent-chat-message.dto.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat}/dtos/agent-chat-thread.dto.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/entities}/agent-chat-message-part.entity.ts (100%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/entities}/agent-chat-message.entity.ts (95%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/entities}/agent-chat-thread.entity.ts (95%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/resolvers}/agent-chat.resolver.ts (87%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/services}/agent-chat.service.ts (83%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat/services}/agent-streaming.service.ts (71%) rename packages/twenty-server/src/engine/metadata-modules/{agent => ai-chat}/utils/mapUIMessagePartsToDBParts.ts (97%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-models/ai-models.module.ts rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-models}/constants/ai-models.const.spec.ts (84%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-models}/constants/ai-models.const.ts (96%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-models}/constants/ai-telemetry.const.ts (100%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-models/constants/dollar-to-credit-multiplier.ts rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-models}/services/ai-model-registry.service.ts (93%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-models}/services/ai.service.ts (83%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-router/services/ai-router-plan-generator.service.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-router/services/ai-router-strategy-decider.service.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-router/types/router-result.interface.ts rename packages/twenty-server/src/engine/{core-modules/ai/ai.module.ts => metadata-modules/ai-tools/ai-tools.module.ts} (62%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-tools}/services/__tests__/tool-adapter.service.spec.ts (98%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-tools}/services/__tests__/tool.service.spec.ts (98%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-tools}/services/tool-adapter.service.ts (100%) rename packages/twenty-server/src/engine/{core-modules/ai => metadata-modules/ai-tools}/services/tool.service.ts (99%) rename packages/twenty-server/src/engine/metadata-modules/{ => search-field-metadata}/constants/search-vector-field.constants.ts (100%) create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-agents/agents/researcher-agent.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-agents/agents/workflow-creation-agent.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 5640ac7455..78f664ea9f 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -113,13 +113,6 @@ export type AgentChatThread = { updatedAt: Scalars['DateTime']; }; -export type AgentHandoff = { - __typename?: 'AgentHandoff'; - description?: Maybe; - id: Scalars['UUID']; - toAgent: Agent; -}; - export type AgentIdInput = { /** The id of the agent. */ id: Scalars['UUID']; @@ -755,12 +748,6 @@ export type CoreViewSort = { workspaceId: Scalars['UUID']; }; -export type CreateAgentHandoffInput = { - description?: InputMaybe; - fromAgentId: Scalars['UUID']; - toAgentId: Scalars['UUID']; -}; - export type CreateAgentInput = { description?: InputMaybe; icon?: InputMaybe; @@ -1754,7 +1741,6 @@ export type Mutation = { checkPublicDomainValidRecords?: Maybe; checkoutSession: BillingSessionOutput; computeStepOutputSchema: Scalars['JSON']; - createAgentHandoff: Scalars['Boolean']; createApiKey: ApiKey; createApprovedAccessDomain: ApprovedAccessDomain; createChatThread: AgentChatThread; @@ -1853,7 +1839,6 @@ export type Mutation = { initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput; initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput; publishServerlessFunction: ServerlessFunction; - removeAgentHandoff: Scalars['Boolean']; removeRoleFromAgent: Scalars['Boolean']; renewToken: AuthTokens; resendEmailVerificationToken: ResendEmailVerificationTokenOutput; @@ -1980,11 +1965,6 @@ export type MutationComputeStepOutputSchemaArgs = { }; -export type MutationCreateAgentHandoffArgs = { - input: CreateAgentHandoffInput; -}; - - export type MutationCreateApiKeyArgs = { input: CreateApiKeyInput; }; @@ -2455,11 +2435,6 @@ export type MutationPublishServerlessFunctionArgs = { }; -export type MutationRemoveAgentHandoffArgs = { - input: RemoveAgentHandoffInput; -}; - - export type MutationRemoveRoleFromAgentArgs = { agentId: Scalars['UUID']; }; @@ -3206,8 +3181,6 @@ export type Query = { currentWorkspace: Workspace; field: Field; fields: FieldConnection; - findAgentHandoffTargets: Array; - findAgentHandoffs: Array; findDistantTablesWithStatus: Array; findManyAgents: Array; findManyApplications: Array; @@ -3323,16 +3296,6 @@ export type QueryFieldsArgs = { }; -export type QueryFindAgentHandoffTargetsArgs = { - input: AgentIdInput; -}; - - -export type QueryFindAgentHandoffsArgs = { - input: AgentIdInput; -}; - - export type QueryFindDistantTablesWithStatusArgs = { input: FindManyRemoteTablesInput; }; @@ -3733,11 +3696,6 @@ export enum RemoteTableStatus { SYNCED = 'SYNCED' } -export type RemoveAgentHandoffInput = { - fromAgentId: Scalars['UUID']; - toAgentId: Scalars['UUID']; -}; - export type ResendEmailVerificationTokenOutput = { __typename?: 'ResendEmailVerificationTokenOutput'; success: Scalars['Boolean']; @@ -4462,6 +4420,7 @@ export type UpdateWorkspaceInput = { defaultRoleId?: InputMaybe; displayName?: InputMaybe; editableProfileFields?: InputMaybe>; + fastModel?: InputMaybe; inviteHash?: InputMaybe; isGoogleAuthBypassEnabled?: InputMaybe; isGoogleAuthEnabled?: InputMaybe; @@ -4472,7 +4431,7 @@ export type UpdateWorkspaceInput = { isPublicInviteLinkEnabled?: InputMaybe; isTwoFactorAuthenticationEnforced?: InputMaybe; logo?: InputMaybe; - routerModel?: InputMaybe; + smartModel?: InputMaybe; subdomain?: InputMaybe; trashRetentionDays?: InputMaybe; }; @@ -4809,6 +4768,7 @@ export type Workspace = { deletedAt?: Maybe; displayName?: Maybe; editableProfileFields?: Maybe>; + fastModel: Scalars['String']; featureFlags?: Maybe>; hasValidEnterpriseKey: Scalars['Boolean']; id: Scalars['UUID']; @@ -4825,6 +4785,7 @@ export type Workspace = { logo?: Maybe; metadataVersion: Scalars['Float']; routerModel: Scalars['String']; + smartModel: Scalars['String']; subdomain: Scalars['String']; trashRetentionDays: Scalars['Float']; updatedAt: Scalars['DateTime']; @@ -4950,13 +4911,6 @@ export type AssignRoleToAgentMutationVariables = Exact<{ export type AssignRoleToAgentMutation = { __typename?: 'Mutation', assignRoleToAgent: boolean }; -export type CreateAgentHandoffMutationVariables = Exact<{ - input: CreateAgentHandoffInput; -}>; - - -export type CreateAgentHandoffMutation = { __typename?: 'Mutation', createAgentHandoff: boolean }; - export type CreateChatThreadMutationVariables = Exact<{ [key: string]: never; }>; @@ -4976,13 +4930,6 @@ export type DeleteOneAgentMutationVariables = Exact<{ export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; -export type RemoveAgentHandoffMutationVariables = Exact<{ - input: RemoveAgentHandoffInput; -}>; - - -export type RemoveAgentHandoffMutation = { __typename?: 'Mutation', removeAgentHandoff: boolean }; - export type RemoveRoleFromAgentMutationVariables = Exact<{ agentId: Scalars['UUID']; }>; @@ -4997,20 +4944,6 @@ export type UpdateOneAgentMutationVariables = Exact<{ export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; -export type FindAgentHandoffTargetsQueryVariables = Exact<{ - input: AgentIdInput; -}>; - - -export type FindAgentHandoffTargetsQuery = { __typename?: 'Query', findAgentHandoffTargets: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, modelId: string, prompt: string, isCustom: boolean, createdAt: string, updatedAt: string }> }; - -export type FindAgentHandoffsQueryVariables = Exact<{ - input: AgentIdInput; -}>; - - -export type FindAgentHandoffsQuery = { __typename?: 'Query', findAgentHandoffs: Array<{ __typename?: 'AgentHandoff', id: string, description?: string | null, toAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, modelId: string, prompt: string, isCustom: boolean, createdAt: string, updatedAt: string } }> }; - export type FindManyAgentsQueryVariables = Exact<{ [key: string]: never; }>; @@ -5981,7 +5914,7 @@ export type BillingSubscriptionFragmentFragment = { __typename?: 'BillingSubscri export type CurrentBillingSubscriptionFragmentFragment = { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, routerModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, fastModel: string, smartModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }; @@ -6007,7 +5940,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, routerModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, hasPassword: boolean, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', id: string, permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isGoogleAuthBypassEnabled: boolean, isMicrosoftAuthBypassEnabled: boolean, isPasswordAuthBypassEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, fastModel: string, smartModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, editableProfileFields?: Array | null, workspaceCustomApplication?: { __typename?: 'Application', id: string } | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; export type ViewFieldFragmentFragment = { __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }; @@ -7018,7 +6951,8 @@ export const UserQueryFragmentFragmentDoc = gql` defaultRole { ...RoleFragment } - routerModel + fastModel + smartModel isTwoFactorAuthenticationEnforced trashRetentionDays editableProfileFields @@ -7173,37 +7107,6 @@ export function useAssignRoleToAgentMutation(baseOptions?: Apollo.MutationHookOp export type AssignRoleToAgentMutationHookResult = ReturnType; export type AssignRoleToAgentMutationResult = Apollo.MutationResult; export type AssignRoleToAgentMutationOptions = Apollo.BaseMutationOptions; -export const CreateAgentHandoffDocument = gql` - mutation CreateAgentHandoff($input: CreateAgentHandoffInput!) { - createAgentHandoff(input: $input) -} - `; -export type CreateAgentHandoffMutationFn = Apollo.MutationFunction; - -/** - * __useCreateAgentHandoffMutation__ - * - * To run a mutation, you first call `useCreateAgentHandoffMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateAgentHandoffMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createAgentHandoffMutation, { data, loading, error }] = useCreateAgentHandoffMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useCreateAgentHandoffMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(CreateAgentHandoffDocument, options); - } -export type CreateAgentHandoffMutationHookResult = ReturnType; -export type CreateAgentHandoffMutationResult = Apollo.MutationResult; -export type CreateAgentHandoffMutationOptions = Apollo.BaseMutationOptions; export const CreateChatThreadDocument = gql` mutation CreateChatThread { createChatThread { @@ -7305,37 +7208,6 @@ export function useDeleteOneAgentMutation(baseOptions?: Apollo.MutationHookOptio export type DeleteOneAgentMutationHookResult = ReturnType; export type DeleteOneAgentMutationResult = Apollo.MutationResult; export type DeleteOneAgentMutationOptions = Apollo.BaseMutationOptions; -export const RemoveAgentHandoffDocument = gql` - mutation RemoveAgentHandoff($input: RemoveAgentHandoffInput!) { - removeAgentHandoff(input: $input) -} - `; -export type RemoveAgentHandoffMutationFn = Apollo.MutationFunction; - -/** - * __useRemoveAgentHandoffMutation__ - * - * To run a mutation, you first call `useRemoveAgentHandoffMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useRemoveAgentHandoffMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [removeAgentHandoffMutation, { data, loading, error }] = useRemoveAgentHandoffMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useRemoveAgentHandoffMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(RemoveAgentHandoffDocument, options); - } -export type RemoveAgentHandoffMutationHookResult = ReturnType; -export type RemoveAgentHandoffMutationResult = Apollo.MutationResult; -export type RemoveAgentHandoffMutationOptions = Apollo.BaseMutationOptions; export const RemoveRoleFromAgentDocument = gql` mutation RemoveRoleFromAgent($agentId: UUID!) { removeRoleFromAgent(agentId: $agentId) @@ -7400,98 +7272,6 @@ export function useUpdateOneAgentMutation(baseOptions?: Apollo.MutationHookOptio export type UpdateOneAgentMutationHookResult = ReturnType; export type UpdateOneAgentMutationResult = Apollo.MutationResult; export type UpdateOneAgentMutationOptions = Apollo.BaseMutationOptions; -export const FindAgentHandoffTargetsDocument = gql` - query FindAgentHandoffTargets($input: AgentIdInput!) { - findAgentHandoffTargets(input: $input) { - id - name - label - description - icon - modelId - prompt - isCustom - createdAt - updatedAt - } -} - `; - -/** - * __useFindAgentHandoffTargetsQuery__ - * - * To run a query within a React component, call `useFindAgentHandoffTargetsQuery` and pass it any options that fit your needs. - * When your component renders, `useFindAgentHandoffTargetsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useFindAgentHandoffTargetsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useFindAgentHandoffTargetsQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(FindAgentHandoffTargetsDocument, options); - } -export function useFindAgentHandoffTargetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(FindAgentHandoffTargetsDocument, options); - } -export type FindAgentHandoffTargetsQueryHookResult = ReturnType; -export type FindAgentHandoffTargetsLazyQueryHookResult = ReturnType; -export type FindAgentHandoffTargetsQueryResult = Apollo.QueryResult; -export const FindAgentHandoffsDocument = gql` - query FindAgentHandoffs($input: AgentIdInput!) { - findAgentHandoffs(input: $input) { - id - description - toAgent { - id - name - label - description - icon - modelId - prompt - isCustom - createdAt - updatedAt - } - } -} - `; - -/** - * __useFindAgentHandoffsQuery__ - * - * To run a query within a React component, call `useFindAgentHandoffsQuery` and pass it any options that fit your needs. - * When your component renders, `useFindAgentHandoffsQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useFindAgentHandoffsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useFindAgentHandoffsQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(FindAgentHandoffsDocument, options); - } -export function useFindAgentHandoffsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(FindAgentHandoffsDocument, options); - } -export type FindAgentHandoffsQueryHookResult = ReturnType; -export type FindAgentHandoffsLazyQueryHookResult = ReturnType; -export type FindAgentHandoffsQueryResult = Apollo.QueryResult; export const FindManyAgentsDocument = gql` query FindManyAgents { findManyAgents { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 0467daa696..9a23a636d9 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -113,13 +113,6 @@ export type AgentChatThread = { updatedAt: Scalars['DateTime']; }; -export type AgentHandoff = { - __typename?: 'AgentHandoff'; - description?: Maybe; - id: Scalars['UUID']; - toAgent: Agent; -}; - export type AgentIdInput = { /** The id of the agent. */ id: Scalars['UUID']; @@ -755,12 +748,6 @@ export type CoreViewSort = { workspaceId: Scalars['UUID']; }; -export type CreateAgentHandoffInput = { - description?: InputMaybe; - fromAgentId: Scalars['UUID']; - toAgentId: Scalars['UUID']; -}; - export type CreateAgentInput = { description?: InputMaybe; icon?: InputMaybe; @@ -1730,10 +1717,8 @@ export type Mutation = { checkPublicDomainValidRecords?: Maybe; checkoutSession: BillingSessionOutput; computeStepOutputSchema: Scalars['JSON']; - createAgentHandoff: Scalars['Boolean']; createApiKey: ApiKey; createApprovedAccessDomain: ApprovedAccessDomain; - createChatThread: AgentChatThread; createCoreView: CoreView; createCoreViewField: CoreViewField; createCoreViewFilter: CoreViewFilter; @@ -1827,7 +1812,6 @@ export type Mutation = { initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput; initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput; publishServerlessFunction: ServerlessFunction; - removeAgentHandoff: Scalars['Boolean']; removeRoleFromAgent: Scalars['Boolean']; renewToken: AuthTokens; resendEmailVerificationToken: ResendEmailVerificationTokenOutput; @@ -1950,11 +1934,6 @@ export type MutationComputeStepOutputSchemaArgs = { }; -export type MutationCreateAgentHandoffArgs = { - input: CreateAgentHandoffInput; -}; - - export type MutationCreateApiKeyArgs = { input: CreateApiKeyInput; }; @@ -2410,11 +2389,6 @@ export type MutationPublishServerlessFunctionArgs = { }; -export type MutationRemoveAgentHandoffArgs = { - input: RemoveAgentHandoffInput; -}; - - export type MutationRemoveRoleFromAgentArgs = { agentId: Scalars['UUID']; }; @@ -3132,17 +3106,12 @@ export type Query = { apiKey?: Maybe; apiKeys: Array; billingPortalSession: BillingSessionOutput; - chatMessages: Array; - chatThread: AgentChatThread; - chatThreads: Array; checkUserExists: CheckUserExistOutput; checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValidOutput; currentUser: User; currentWorkspace: Workspace; field: Field; fields: FieldConnection; - findAgentHandoffTargets: Array; - findAgentHandoffs: Array; findManyAgents: Array; findManyApplications: Array; findManyCronTriggers: Array; @@ -3223,16 +3192,6 @@ export type QueryBillingPortalSessionArgs = { }; -export type QueryChatMessagesArgs = { - threadId: Scalars['UUID']; -}; - - -export type QueryChatThreadArgs = { - id: Scalars['UUID']; -}; - - export type QueryCheckUserExistsArgs = { captchaToken?: InputMaybe; email: Scalars['String']; @@ -3244,16 +3203,6 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = { }; -export type QueryFindAgentHandoffTargetsArgs = { - input: AgentIdInput; -}; - - -export type QueryFindAgentHandoffsArgs = { - input: AgentIdInput; -}; - - export type QueryFindOneAgentArgs = { input: AgentIdInput; }; @@ -3603,11 +3552,6 @@ export enum RemoteTableStatus { SYNCED = 'SYNCED' } -export type RemoveAgentHandoffInput = { - fromAgentId: Scalars['UUID']; - toAgentId: Scalars['UUID']; -}; - export type ResendEmailVerificationTokenOutput = { __typename?: 'ResendEmailVerificationTokenOutput'; success: Scalars['Boolean']; @@ -4324,6 +4268,7 @@ export type UpdateWorkspaceInput = { defaultRoleId?: InputMaybe; displayName?: InputMaybe; editableProfileFields?: InputMaybe>; + fastModel?: InputMaybe; inviteHash?: InputMaybe; isGoogleAuthBypassEnabled?: InputMaybe; isGoogleAuthEnabled?: InputMaybe; @@ -4334,7 +4279,7 @@ export type UpdateWorkspaceInput = { isPublicInviteLinkEnabled?: InputMaybe; isTwoFactorAuthenticationEnforced?: InputMaybe; logo?: InputMaybe; - routerModel?: InputMaybe; + smartModel?: InputMaybe; subdomain?: InputMaybe; trashRetentionDays?: InputMaybe; }; @@ -4661,6 +4606,7 @@ export type Workspace = { deletedAt?: Maybe; displayName?: Maybe; editableProfileFields?: Maybe>; + fastModel: Scalars['String']; featureFlags?: Maybe>; hasValidEnterpriseKey: Scalars['Boolean']; id: Scalars['UUID']; @@ -4677,6 +4623,7 @@ export type Workspace = { logo?: Maybe; metadataVersion: Scalars['Float']; routerModel: Scalars['String']; + smartModel: Scalars['String']; subdomain: Scalars['String']; trashRetentionDays: Scalars['Float']; updatedAt: Scalars['DateTime']; diff --git a/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx b/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx index 8437a95c71..ca83883741 100644 --- a/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx +++ b/packages/twenty-front/src/modules/ai/components/RoutingDebugDisplay.tsx @@ -246,7 +246,8 @@ const DetailsTab = ({ debug, copyToClipboard }: DetailsTabProps) => { id: debug.selectedAgentId, label: debug.selectedAgentLabel, }, - routerModel: debug.routerModel, + fastModel: debug.fastModel, + smartModel: debug.smartModel, agentModel: debug.agentModel, availableAgents: debug.availableAgents, }; diff --git a/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx b/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx index 6e4a0110df..9661a3d98e 100644 --- a/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx +++ b/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx @@ -1,47 +1,57 @@ import { RoutingDebugDisplay } from '@/ai/components/RoutingDebugDisplay'; import { ShimmeringText } from '@/ai/components/ShimmeringText'; +import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import { useState } from 'react'; import { type DataMessagePart } from 'twenty-shared/ai'; -import { IconCpu, IconSparkles } from 'twenty-ui/display'; +import { IconChevronDown, IconChevronUp, IconCpu } from 'twenty-ui/display'; +import { AnimatedExpandableContainer } from 'twenty-ui/layout'; -const StyledRoutingContainer = styled.div` - align-items: center; - background: ${({ theme }) => theme.background.transparent.lighter}; - border: ${({ theme }) => `1px dashed ${theme.border.color.medium}`}; - border-radius: ${({ theme }) => theme.border.radius.md}; +const StyledContainer = styled.div` display: flex; - font-size: ${({ theme }) => theme.font.size.sm}; + flex-direction: column; gap: ${({ theme }) => theme.spacing(2)}; - margin-bottom: ${({ theme }) => theme.spacing(2)}; - padding: ${({ theme }) => theme.spacing(2, 3)}; - width: fit-content; `; -const StyledIconContainer = styled.div<{ isLoading: boolean }>` +const StyledToggleButton = styled.div<{ isExpandable: boolean }>` align-items: center; - animation: ${({ isLoading }) => - isLoading ? 'pulseAnimation 2s ease-in-out infinite' : 'none'}; - color: ${({ theme }) => theme.color.blue}; + background: none; + border: none; + cursor: ${({ isExpandable }) => (isExpandable ? 'pointer' : 'auto')}; display: flex; + color: ${({ theme }) => theme.font.color.tertiary}; + gap: ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(1)} 0; + transition: color ${({ theme }) => theme.animation.duration.normal}s; - @keyframes pulseAnimation { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } + &:hover { + color: ${({ isExpandable, theme }) => + isExpandable ? theme.font.color.secondary : theme.font.color.tertiary}; } `; -const StyledText = styled.div` +const StyledDisplayMessage = styled.span` color: ${({ theme }) => theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.md}; + font-weight: ${({ theme }) => theme.font.weight.medium}; `; -const StyledWrapper = styled.div` +const StyledIconTextContainer = styled.div` display: flex; - flex-direction: column; + align-items: center; + gap: ${({ theme }) => theme.spacing(1)}; + + svg { + min-width: ${({ theme }) => theme.icon.size.sm}px; + } +`; + +const StyledContentContainer = styled.div` + background: ${({ theme }) => theme.background.transparent.lighter}; + border: 1px solid ${({ theme }) => theme.border.color.light}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + min-width: 0; + padding: ${({ theme }) => theme.spacing(3)}; `; export const RoutingStatusDisplay = ({ @@ -49,28 +59,54 @@ export const RoutingStatusDisplay = ({ }: { data: DataMessagePart['routing-status']; }) => { + const theme = useTheme(); + const [isExpanded, setIsExpanded] = useState(false); const isLoading = data.state === 'loading'; const isDebugMode = process.env.IS_DEBUG_MODE === 'true'; + const isExpandable = isDebugMode && data.state === 'routed' && data.debug; if (data.state === 'error') { return null; } + if (isLoading) { + return ( + + + + + {data.text} + + + + ); + } + return ( - - - - {isLoading ? : } - - {isLoading ? ( - {data.text} - ) : ( - {data.text} - )} - - {isDebugMode && data.state === 'routed' && data.debug && ( - + + isExpandable && setIsExpanded(!isExpanded)} + isExpandable={!!isExpandable} + > + + + {data.text} + + {isExpandable && + (isExpanded ? ( + + ) : ( + + ))} + + + {isExpandable && ( + + + + + )} - + ); }; diff --git a/packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts b/packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts new file mode 100644 index 0000000000..84e98b8308 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts @@ -0,0 +1 @@ +export const DEFAULT_FAST_MODEL = 'default-fast-model' as const; diff --git a/packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts b/packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts new file mode 100644 index 0000000000..f85d6f9719 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts @@ -0,0 +1 @@ +export const DEFAULT_SMART_MODEL = 'default-smart-model' as const; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/createAgentHandoff.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/createAgentHandoff.ts deleted file mode 100644 index 31bbbdbde9..0000000000 --- a/packages/twenty-front/src/modules/ai/graphql/mutations/createAgentHandoff.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { gql } from '@apollo/client'; - -export const CREATE_AGENT_HANDOFF = gql` - mutation CreateAgentHandoff($input: CreateAgentHandoffInput!) { - createAgentHandoff(input: $input) - } -`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/removeAgentHandoff.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/removeAgentHandoff.ts deleted file mode 100644 index a317b61940..0000000000 --- a/packages/twenty-front/src/modules/ai/graphql/mutations/removeAgentHandoff.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { gql } from '@apollo/client'; - -export const REMOVE_AGENT_HANDOFF = gql` - mutation RemoveAgentHandoff($input: RemoveAgentHandoffInput!) { - removeAgentHandoff(input: $input) - } -`; diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffTargets.ts b/packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffTargets.ts deleted file mode 100644 index 1fe34da9a3..0000000000 --- a/packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffTargets.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { gql } from '@apollo/client'; - -export const FIND_AGENT_HANDOFF_TARGETS = gql` - query FindAgentHandoffTargets($input: AgentIdInput!) { - findAgentHandoffTargets(input: $input) { - id - name - label - description - icon - modelId - prompt - isCustom - createdAt - updatedAt - } - } -`; diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffs.ts b/packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffs.ts deleted file mode 100644 index 45565d670d..0000000000 --- a/packages/twenty-front/src/modules/ai/graphql/queries/findAgentHandoffs.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { gql } from '@apollo/client'; - -export const FIND_AGENT_HANDOFFS = gql` - query FindAgentHandoffs($input: AgentIdInput!) { - findAgentHandoffs(input: $input) { - id - description - toAgent { - id - name - label - description - icon - modelId - prompt - isCustom - createdAt - updatedAt - } - } - } -`; diff --git a/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts b/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts index 744cb4ae4b..46ca78ed45 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts @@ -2,13 +2,20 @@ import { aiModelsState } from '@/client-config/states/aiModelsState'; import { useRecoilValue } from 'recoil'; import { type SelectOption } from 'twenty-ui/input'; +import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; +import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; + export const useAiModelOptions = (): SelectOption[] => { const aiModels = useRecoilValue(aiModelsState); return aiModels .map((model) => ({ value: model.modelId, - label: `${model.label} (${model.provider})`, + label: + model.modelId === DEFAULT_FAST_MODEL || + model.modelId === DEFAULT_SMART_MODEL + ? model.label + : `${model.label} (${model.provider})`, })) .sort((a, b) => a.label.localeCompare(b.label)); }; diff --git a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts index 16d24d2903..ad49b6f402 100644 --- a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts +++ b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts @@ -1,6 +1,8 @@ import { ApolloError, gql, InMemoryCache } from '@apollo/client'; import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'; +import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; +import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant'; import { WorkspaceActivationStatus } from '~/generated/graphql'; import { ApolloFactory, type Options } from '../apollo.factory'; @@ -62,6 +64,8 @@ const mockWorkspace = { }, isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, + fastModel: DEFAULT_FAST_MODEL, + smartModel: DEFAULT_SMART_MODEL, routerModel: 'auto', workspaceCustomApplication: CUSTOM_WORKSPACE_APPLICATION_MOCK, workspaceCustomApplicationId: CUSTOM_WORKSPACE_APPLICATION_MOCK.id, diff --git a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts index 92e9f34910..38c584c982 100644 --- a/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts +++ b/packages/twenty-front/src/modules/auth/states/currentWorkspaceState.ts @@ -32,7 +32,8 @@ export type CurrentWorkspace = Pick< | 'metadataVersion' | 'isTwoFactorAuthenticationEnforced' | 'trashRetentionDays' - | 'routerModel' + | 'fastModel' + | 'smartModel' | 'editableProfileFields' > & { defaultRole?: Omit | null; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts index 6a4b87b173..3aed48ae4b 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts @@ -4,6 +4,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant'; import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; +import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { SubscriptionInterval, SubscriptionStatus, @@ -58,7 +60,8 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ ], isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, - routerModel: 'auto', + fastModel: DEFAULT_FAST_MODEL, + smartModel: DEFAULT_SMART_MODEL, }); }, }); diff --git a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts index 5ef403ab6b..a0ba771ef4 100644 --- a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts +++ b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts @@ -82,7 +82,8 @@ export const USER_QUERY_FRAGMENT = gql` defaultRole { ...RoleFragment } - routerModel + fastModel + smartModel isTwoFactorAuthenticationEnforced trashRetentionDays editableProfileFields diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIRouterSettings.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIRouterSettings.tsx index 321af7e091..be4ae71547 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIRouterSettings.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIRouterSettings.tsx @@ -2,6 +2,8 @@ import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions'; +import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; +import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { StyledSettingsOptionCardContent, @@ -13,7 +15,7 @@ import { SettingsOptionIconCustomizer } from '@/settings/components/SettingsOpti import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Select } from '@/ui/input/components/Select'; import { t } from '@lingui/core/macro'; -import { H2Title, IconCpu } from 'twenty-ui/display'; +import { H2Title, IconBolt, IconBrain } from 'twenty-ui/display'; import { Card, Section } from 'twenty-ui/layout'; import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql'; @@ -39,39 +41,76 @@ export const SettingsAIRouterSettings = () => { const modelOptions = useAiModelOptions(); const noModelsAvailable = modelOptions.length === 0; - const handleModelChange = async (value: string) => { + const handleFastModelChange = async (value: string) => { if (!currentWorkspace?.id) { return; } const newValue = value; - const previousValue = currentWorkspace?.routerModel || 'auto'; + const previousValue = currentWorkspace?.fastModel || DEFAULT_FAST_MODEL; try { setCurrentWorkspace({ ...currentWorkspace, - routerModel: newValue, + fastModel: newValue, }); await updateWorkspace({ variables: { input: { - routerModel: newValue, + fastModel: newValue, }, }, }); enqueueSuccessSnackBar({ - message: t`Router model updated successfully`, + message: t`Fast model updated successfully`, }); } catch { setCurrentWorkspace({ ...currentWorkspace, - routerModel: previousValue, + fastModel: previousValue, }); enqueueErrorSnackBar({ - message: t`Failed to update router model`, + message: t`Failed to update fast model`, + }); + } + }; + + const handleSmartModelChange = async (value: string) => { + if (!currentWorkspace?.id) { + return; + } + + const newValue = value; + const previousValue = currentWorkspace?.smartModel || DEFAULT_SMART_MODEL; + + try { + setCurrentWorkspace({ + ...currentWorkspace, + smartModel: newValue, + }); + + await updateWorkspace({ + variables: { + input: { + smartModel: newValue, + }, + }, + }); + + enqueueSuccessSnackBar({ + message: t`Smart model updated successfully`, + }); + } catch { + setCurrentWorkspace({ + ...currentWorkspace, + smartModel: previousValue, + }); + + enqueueErrorSnackBar({ + message: t`Failed to update smart model`, }); } }; @@ -79,40 +118,67 @@ export const SettingsAIRouterSettings = () => { return (
- - - - - -
- - {t`Router Model`} - - - {t`Fast model to route to the right agent`} - -
- - {noModelsAvailable ? ( - - {t`No models available. Please configure AI models in your workspace settings.`} - - ) : ( + {noModelsAvailable ? ( + + + + {t`No models available. Please configure AI models in your workspace settings.`} + + + + ) : ( + + + + + +
+ + {t`Fast Model`} + + + {t`Quick model for routing decisions`} + +
+ + +
+
+ )}
); }; diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentHandoffForm.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentHandoffForm.tsx deleted file mode 100644 index ddfad292b0..0000000000 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentHandoffForm.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import styled from '@emotion/styled'; -import { useLingui } from '@lingui/react/macro'; -import { useState } from 'react'; - -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { Select } from '@/ui/input/components/Select'; -import { TextArea } from '@/ui/input/components/TextArea'; -import { IconPlus } from 'twenty-ui/display'; -import { Button, type SelectOption } from 'twenty-ui/input'; -import { useCreateAgentHandoffMutation } from '~/generated-metadata/graphql'; - -const StyledAddHandoffForm = styled.div` - background: ${({ theme }) => theme.background.secondary}; - border: 1px solid ${({ theme }) => theme.border.color.medium}; - border-radius: ${({ theme }) => theme.border.radius.md}; - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(3)}; - padding: ${({ theme }) => theme.spacing(3)}; - margin-top: ${({ theme }) => theme.spacing(3)}; -`; - -const StyledFormActions = styled.div` - display: flex; - gap: ${({ theme }) => theme.spacing(2)}; - justify-content: flex-end; -`; - -const StyledAddButtonContainer = styled.div` - display: flex; - justify-content: flex-end; - margin-top: ${({ theme }) => theme.spacing(3)}; -`; - -type SettingsAgentHandoffFormProps = { - agentId: string; - availableAgentOptions: SelectOption[]; - agentsLoading: boolean; - onHandoffAdded: () => void; -}; - -export const SettingsAgentHandoffForm = ({ - agentId, - availableAgentOptions, - agentsLoading, - onHandoffAdded, -}: SettingsAgentHandoffFormProps) => { - const { t } = useLingui(); - const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); - - const [isAddingHandoff, setIsAddingHandoff] = useState(false); - const [selectedTargetAgentId, setSelectedTargetAgentId] = useState(''); - const [handoffDescription, setHandoffDescription] = useState(''); - - const [createAgentHandoff] = useCreateAgentHandoffMutation(); - - const noAvailableAgents = availableAgentOptions.length === 0; - - const resetHandoffForm = () => { - setIsAddingHandoff(false); - setSelectedTargetAgentId(''); - setHandoffDescription(''); - }; - - const handleAddHandoff = async () => { - try { - await createAgentHandoff({ - variables: { - input: { - fromAgentId: agentId, - toAgentId: selectedTargetAgentId, - description: handoffDescription, - }, - }, - }); - - onHandoffAdded(); - resetHandoffForm(); - enqueueSuccessSnackBar({ - message: t`Handoff created successfully`, - }); - } catch { - enqueueErrorSnackBar({ - message: t`Failed to create handoff`, - }); - } - }; - - return ( - <> - {isAddingHandoff ? ( - -