Files
twenty/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-creation.integration-spec.ts
T
Félix Malfait 908aefe7c1 feat: replace hardcoded AI model constants with JSON seed catalog (#18818)
## Summary

- Replaces per-provider TypeScript constant files
(`openai-models.const.ts`, `anthropic-models.const.ts`, etc.) with a
single `ai-providers.json` catalog as the source of truth
- Adds runtime model discovery via AI SDK for self-hosted providers,
with `models.dev` enrichment for pricing/capabilities
- Introduces composite model IDs (`provider/modelId`) for canonical,
conflict-free identification
- Simplifies provider configuration: API keys are injected from
environment variables (e.g., `OPENAI_API_KEY`)
- Adds admin panel UI for provider management (add/remove/test), model
discovery, recommended model configuration, and default fast/smart model
selection per workspace
- Removes deprecated config variables (`AI_DISABLED_MODEL_IDS`,
`AUTO_ENABLE_NEW_AI_MODELS`, etc.)
- Adds database migration for composite model ID format

## Test plan

- [ ] Server typecheck passes
- [ ] Frontend typecheck passes
- [ ] Server unit tests pass
- [ ] Frontend unit tests pass
- [ ] CI pipeline green
- [ ] Admin panel AI tab loads correctly
- [ ] Provider discovery works for configured providers
- [ ] Model recommendation toggles persist
- [ ] Default fast/smart model selection works


Made with [Cursor](https://cursor.com)
2026-03-21 16:03:58 +01:00

218 lines
6.1 KiB
TypeScript

import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util';
import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util';
import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util';
import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util';
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input';
describe('Agent creation should succeed', () => {
let createdAgentId: string;
afterEach(async () => {
if (createdAgentId) {
await deleteOneAgent({
expectToFail: false,
input: { id: createdAgentId },
});
}
});
it('should create a basic custom agent with minimal input', async () => {
const { data } = await createOneAgent({
expectToFail: false,
input: {
label: 'Test Agent',
prompt: 'You are a helpful test assistant',
modelId: 'openai/gpt-4.1',
},
});
createdAgentId = data?.createOneAgent?.id;
expect(data.createOneAgent).toMatchObject({
id: expect.any(String),
name: 'testAgent',
label: 'Test Agent',
icon: null,
description: null,
prompt: 'You are a helpful test assistant',
modelId: 'openai/gpt-4.1',
responseFormat: { type: 'text' },
roleId: null,
isCustom: true,
modelConfiguration: null,
evaluationInputs: [],
});
});
it('should create agent with all optional fields', async () => {
const input = {
name: 'customAgentName',
label: 'Custom Agent Label',
icon: 'IconRobot',
description: 'A custom agent with all fields specified',
prompt: 'You are a specialized assistant for testing',
modelId: 'openai/gpt-5.2',
responseFormat: { type: 'text' },
modelConfiguration: {
webSearch: {
enabled: true,
configuration: {
maxTokens: 1000,
temperature: 0.7,
},
},
twitterSearch: {
enabled: true,
configuration: {},
},
},
evaluationInputs: ['test input 1', 'test input 2'],
} as const satisfies CreateAgentInput;
const { data } = await createOneAgent({
expectToFail: false,
input,
});
createdAgentId = data?.createOneAgent?.id;
expect(data.createOneAgent).toMatchObject({
id: expect.any(String),
...input,
});
});
it('should create agent with JSON response format', async () => {
const { data } = await createOneAgent({
expectToFail: false,
input: {
label: 'JSON Response Agent',
prompt: 'Return structured JSON data',
modelId: 'openai/gpt-4.1',
responseFormat: {
type: 'json',
schema: {
type: 'object',
properties: {
result: { type: 'string' },
confidence: { type: 'number' },
},
},
},
},
});
createdAgentId = data?.createOneAgent?.id;
expect(data.createOneAgent).toMatchObject({
id: expect.any(String),
label: 'JSON Response Agent',
prompt: 'Return structured JSON data',
responseFormat: {
type: 'json',
schema: {
type: 'object',
properties: {
result: { type: 'string' },
confidence: { type: 'number' },
},
},
},
});
});
it('should create agent and automatically compute name from label', async () => {
const { data } = await createOneAgent({
expectToFail: false,
input: {
label: 'My Test Agent With Spaces',
prompt: 'Testing name computation',
modelId: 'openai/gpt-4.1',
},
});
createdAgentId = data?.createOneAgent?.id;
expect(data.createOneAgent).toMatchObject({
id: expect.any(String),
name: 'myTestAgentWithSpaces',
label: 'My Test Agent With Spaces',
});
});
it('should sanitize input by trimming whitespace', async () => {
const { data } = await createOneAgent({
expectToFail: false,
input: {
name: ' agentWithSpaces ',
label: ' Agent With Spaces ',
icon: ' IconRobot ',
description: ' Description with spaces ',
prompt: ' Prompt with spaces ',
modelId: 'openai/gpt-4.1',
},
});
createdAgentId = data?.createOneAgent?.id;
expect(data.createOneAgent).toMatchObject({
id: expect.any(String),
name: 'agentWithSpaces',
label: 'Agent With Spaces',
icon: 'IconRobot',
description: 'Description with spaces',
prompt: 'Prompt with spaces',
});
});
it('should create agent with role assignment', async () => {
// First, create a role that can be assigned to agents
const { data: roleData } = await createOneRole({
expectToFail: false,
input: {
label: 'Test Agent Role',
description: 'A role for agent testing',
canUpdateAllSettings: false,
canAccessAllTools: true,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canBeAssignedToUsers: false,
canBeAssignedToAgents: true,
canBeAssignedToApiKeys: false,
},
});
const createdRoleId = roleData?.createOneRole?.id;
// Create agent with role assignment
const { data } = await createOneAgent({
expectToFail: false,
input: {
label: 'Agent With Role',
prompt: 'Agent with role assignment',
modelId: 'openai/gpt-4.1',
roleId: createdRoleId,
},
});
createdAgentId = data?.createOneAgent?.id;
expect(data.createOneAgent).toMatchObject({
id: expect.any(String),
label: 'Agent With Role',
prompt: 'Agent with role assignment',
modelId: 'openai/gpt-4.1',
roleId: createdRoleId,
isCustom: true,
});
// Clean up the role
await deleteOneRole({
expectToFail: false,
input: { idToDelete: createdRoleId },
});
});
});