refactor: standardize AI acronym to Ai (PascalCase) across internal identifiers (#19837)

## Summary

The "AI" acronym was rendered inconsistently across the codebase. The
backend AI module had settled on PascalCase `Ai` (`AiAgentModule`,
`AiBillingService`, `AiChatModule`, `AiModelRegistryService`, etc.),
while frontend components, several DTOs, a few types, and shared
identifiers still used all-caps `AI` (`AIChatTab`,
`AISystemPromptPreviewDTO`, `SettingsPath.AIPrompts`, ...). CLAUDE.md
specifies PascalCase for classes; this PR normalizes everything internal
to `Ai`.

**This is a pure internal rename.** The GraphQL schema is untouched —
`@ObjectType` decorator string arguments, resolver method names (which
become Query/Mutation field names), gql template contents, and the
`generated-metadata/graphql.ts` file are preserved verbatim. The only
visible change is TypeScript identifiers and file names.

## Also folded in (adjacent cleanups)

- **`AgentModelConfigService` → `AiModelConfigService`**. Lives in
`ai-models/` and is used by multiple AI code paths, not just the Agent
entity. The "Agent" prefix was misleading.
- **`generate-text-input.dto.ts` → `generate-text.input.ts`**. The
`ai-agent/dtos/` folder already uses `<entity>.input.ts` convention for
Input classes (`create-agent.input.ts` etc.); the old path mixed
`.dto.ts` file extension with a class that has no DTO suffix. File
rename only; class stays `GenerateTextInput`.
- **Removed stale TODO** in `ai-model-config.type.ts` that asked for the
`AiModelConfig` rename that this PR performs.

## Rename methodology

Bulk rename via perl with anchored regex
`(?<!['"])(?<![A-Z.])AI([A-Z])(?=[a-z])/Ai$1/g`:

- **Lookbehind for non-uppercase** skips adjacent acronyms (`MOSAIC`,
`OIDCSSO`) and leaves `AIRBNB_ID` alone.
- **Lookbehind for non-quote** protects most string literals.
- **Lookahead for lowercase** restricts matches to PascalCase
identifiers (`AIChatTab`), leaving SCREAMING_SNAKE constants untouched.

Strict file-scope exclusions: `generated-metadata/**`, `generated/**`,
`locales/**`, `migrations/**`, `illustrations/**`, `halftone/**`, and
the two gql template files (`queries/getAISystemPromptPreview.ts`,
`mutations/uploadAIChatFile.ts`).

Post-rename reverts for identifiers where the regex was too eager:
- Backend resolver method names kept: `getAISystemPromptPreview`,
`uploadAIChatFile` (they are GraphQL field names).
- `@ObjectType('AdminAIModels')` / `('AISystemPromptPreview')` /
`('AISystemPromptSection')` kept as-is.
- Backend classes `ClientAIModelConfig` / `AdminAIModelConfig` kept
as-is (they use `@ObjectType()` with no argument, so the class name IS
the schema name).
- External-library symbols restored: `OpenAIProvider`,
`createOpenAICompatible`, `vercelAIIntegration`.

File renames use a two-step rename to work on macOS case-insensitive
filesystems: `git mv X.tsx X.tsx.tmp && git mv X.tsx.tmp renamed.tsx`.

## Diff audit

- 0 changes to migrations
- 0 changes to locale `.po` / `.ts` files
- 0 changes to `generated-metadata/graphql.ts`
- 0 changes to website illustration files (base64 blobs preserved)
- 0 renames inside user-facing translation strings (`t\`…\``,
`msg\`…\``, `<Trans>…</Trans>`)

## Test plan

- [x] `npx nx typecheck twenty-server` — PASS
- [x] `npx nx typecheck twenty-front` — PASS
- [x] `npx jest ai-model admin agent-role` — 79/79 PASS
- [x] `npx oxlint --type-aware` on 118 changed files — 0 errors
- [x] `npx prettier --check` on 118 changed files — clean
- [ ] CI
This commit is contained in:
Félix Malfait
2026-04-19 13:29:35 +02:00
committed by GitHub
parent 1e27c3b621
commit 6117a1d6c0
126 changed files with 629 additions and 630 deletions
@@ -31,7 +31,7 @@ import { WORKFLOW_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
import { repairToolCall } from 'src/engine/metadata-modules/ai/ai-agent/utils/repair-tool-call.util';
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/agent-model-config.service';
import { AiModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
@@ -46,7 +46,7 @@ export class AgentAsyncExecutorService {
constructor(
private readonly aiModelRegistryService: AiModelRegistryService,
private readonly agentModelConfigService: AgentModelConfigService,
private readonly aiModelConfigService: AiModelConfigService,
private readonly toolRegistry: ToolRegistryService,
@InjectRepository(RoleTargetEntity)
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
@@ -170,10 +170,10 @@ export class AgentAsyncExecutorService {
},
);
providerOptions = this.agentModelConfigService.getProviderOptions(
providerOptions = this.aiModelConfigService.getProviderOptions(
registeredModel,
agent as unknown as Parameters<
typeof this.agentModelConfigService.getProviderOptions
typeof this.aiModelConfigService.getProviderOptions
>[1],
);
}
@@ -1,4 +1,4 @@
import { type AIModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { type AiModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { ModelFamily } from 'src/engine/metadata-modules/ai/ai-models/types/model-family.enum';
export type TokenUsageInput = {
@@ -36,7 +36,7 @@ const safeNumber = (value: number | undefined): number => {
// Anthropic: outputTokens excludes reasoning (thinking) tokens
// OpenAI/xAI/Groq/Google: outputTokens includes reasoning tokens
export const computeCostBreakdown = (
model: AIModelConfig,
model: AiModelConfig,
usage: TokenUsageInput,
): CostBreakdown => {
const rawInputTokens = safeNumber(usage.inputTokens);
@@ -1,7 +1,7 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
@ObjectType('AISystemPromptSection')
export class AISystemPromptSectionDTO {
@ObjectType('AiSystemPromptSection')
export class AiSystemPromptSectionDTO {
@Field(() => String)
title: string;
@@ -12,10 +12,10 @@ export class AISystemPromptSectionDTO {
estimatedTokenCount: number;
}
@ObjectType('AISystemPromptPreview')
export class AISystemPromptPreviewDTO {
@Field(() => [AISystemPromptSectionDTO])
sections: AISystemPromptSectionDTO[];
@ObjectType('AiSystemPromptPreview')
export class AiSystemPromptPreviewDTO {
@Field(() => [AiSystemPromptSectionDTO])
sections: AiSystemPromptSectionDTO[];
@Field(() => Int)
estimatedTokenCount: number;
@@ -25,7 +25,7 @@ import { AgentChatStreamingService } from 'src/engine/metadata-modules/ai/ai-cha
import { AgentChatService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service';
import { ChatExecutionService } from 'src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service';
import { getCancelChannel } from 'src/engine/metadata-modules/ai/ai-chat/utils/get-cancel-channel.util';
import type { AIModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import type { AiModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { STREAM_AGENT_CHAT_JOB_NAME } from './stream-agent-chat-job-name.constant';
import { type StreamAgentChatJobData } from './stream-agent-chat-job.types';
@@ -351,7 +351,7 @@ export class StreamAgentChatJob {
};
providerMetadata?: Record<string, Record<string, unknown> | undefined>;
};
modelConfig: AIModelConfig;
modelConfig: AiModelConfig;
lastStepConversationSize: number;
totalCacheCreationTokens: number;
onUpdateUsage: (usage: {
@@ -435,7 +435,7 @@ export class StreamAgentChatJob {
outputCredits: number;
};
lastStepConversationSize: number;
modelConfig: AIModelConfig;
modelConfig: AiModelConfig;
userMessagePromise: Promise<{ turnId: string | null }>;
}): Promise<void> {
if (responseMessage.parts.length === 0) {
@@ -43,7 +43,7 @@ import { AiGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/ai
import { type BrowsingContextType } from 'src/engine/metadata-modules/ai/ai-agent/types/browsingContext.type';
import { AgentMessageDTO } from 'src/engine/metadata-modules/ai/ai-agent-execution/dtos/agent-message.dto';
import { AgentChatThreadDTO } from 'src/engine/metadata-modules/ai/ai-chat/dtos/agent-chat-thread.dto';
import { AISystemPromptPreviewDTO } from 'src/engine/metadata-modules/ai/ai-chat/dtos/ai-system-prompt-preview.dto';
import { AiSystemPromptPreviewDTO } from 'src/engine/metadata-modules/ai/ai-chat/dtos/ai-system-prompt-preview.dto';
import { ChatStreamCatchupChunksDTO } from 'src/engine/metadata-modules/ai/ai-chat/dtos/chat-stream-catchup-chunks.dto';
import { SendChatMessageResultDTO } from 'src/engine/metadata-modules/ai/ai-chat/dtos/send-chat-message-result.dto';
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
@@ -275,9 +275,9 @@ export class AgentChatResolver {
return deleted;
}
@Query(() => AISystemPromptPreviewDTO)
@Query(() => AiSystemPromptPreviewDTO)
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
async getAISystemPromptPreview(
async getAiSystemPromptPreview(
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthUserWorkspaceId() userWorkspaceId: string,
) {
@@ -55,10 +55,10 @@ import {
import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const';
import {
AiModelRegistryService,
type RegisteredAIModel,
type RegisteredAiModel,
} from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
import { SdkProviderFactoryService } from 'src/engine/metadata-modules/ai/ai-models/services/sdk-provider-factory.service';
import { type AIModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { type AiModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { WebSearchService } from 'src/engine/core-modules/web-search/web-search.service';
import { SkillService } from 'src/engine/metadata-modules/skill/skill.service';
@@ -76,7 +76,7 @@ export type ChatExecutionOptions = {
export type ChatExecutionResult = {
stream: ReturnType<typeof streamText>;
modelConfig: AIModelConfig;
modelConfig: AiModelConfig;
};
@Injectable()
@@ -455,7 +455,7 @@ export class ChatExecutionService {
return context;
}
private getNativeWebSearchTools(model: RegisteredAIModel): {
private getNativeWebSearchTools(model: RegisteredAiModel): {
tools: ToolSet;
callableToolNames: string[];
} {
@@ -14,7 +14,7 @@ import {
AiExceptionCode,
} from 'src/engine/metadata-modules/ai/ai.exception';
import { AiRestApiExceptionFilter } from 'src/engine/metadata-modules/ai/filters/ai-api-exception.filter';
import { GenerateTextInput } from 'src/engine/metadata-modules/ai/ai-generate-text/dtos/generate-text-input.dto';
import { GenerateTextInput } from 'src/engine/metadata-modules/ai/ai-generate-text/dtos/generate-text.input';
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
@Controller('rest/ai')
@@ -1,6 +1,6 @@
import { Global, Module } from '@nestjs/common';
import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/agent-model-config.service';
import { AiModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-config.service';
import { AiModelPreferencesService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-preferences.service';
import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
import { ModelsDevCatalogService } from 'src/engine/metadata-modules/ai/ai-models/services/models-dev-catalog.service';
@@ -15,11 +15,11 @@ import { SdkProviderFactoryService } from 'src/engine/metadata-modules/ai/ai-mod
ModelsDevCatalogService,
AiModelPreferencesService,
AiModelRegistryService,
AgentModelConfigService,
AiModelConfigService,
],
exports: [
AiModelRegistryService,
AgentModelConfigService,
AiModelConfigService,
SdkProviderFactoryService,
ModelsDevCatalogService,
],
@@ -12,20 +12,20 @@ import {
} from 'src/engine/metadata-modules/ai/ai-models/constants/ai-sdk-package.const';
import {
AiModelRegistryService,
RegisteredAIModel,
RegisteredAiModel,
} from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service';
import { SdkProviderFactoryService } from 'src/engine/metadata-modules/ai/ai-models/services/sdk-provider-factory.service';
import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
@Injectable()
export class AgentModelConfigService {
export class AiModelConfigService {
constructor(
private readonly aiModelRegistryService: AiModelRegistryService,
private readonly sdkProviderFactory: SdkProviderFactoryService,
) {}
getProviderOptions(
model: RegisteredAIModel,
model: RegisteredAiModel,
agent: FlatAgentWithRoleId,
): ProviderOptions {
switch (model.sdkPackage) {
@@ -41,7 +41,7 @@ export class AgentModelConfigService {
}
getNativeModelTools(
model: RegisteredAIModel,
model: RegisteredAiModel,
agent: FlatAgentWithRoleId,
): ToolSet {
const tools: ToolSet = {};
@@ -123,7 +123,7 @@ export class AgentModelConfigService {
}
private getAnthropicProviderOptions(
model: RegisteredAIModel,
model: RegisteredAiModel,
): ProviderOptions {
if (!model.supportsReasoning) {
return {};
@@ -139,7 +139,7 @@ export class AgentModelConfigService {
};
}
private getBedrockProviderOptions(model: RegisteredAIModel): ProviderOptions {
private getBedrockProviderOptions(model: RegisteredAiModel): ProviderOptions {
if (!model.supportsReasoning) {
return {};
}
@@ -14,7 +14,7 @@ import {
import { AiModelPreferencesService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-preferences.service';
import { ProviderConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/provider-config.service';
import { SdkProviderFactoryService } from 'src/engine/metadata-modules/ai/ai-models/services/sdk-provider-factory.service';
import { type AIModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { type AiModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-model-config.type';
import { type AiProviderConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-config.type';
import { type AiProviderModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-model-config.type';
import { type AiProvidersConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-providers-config.type';
@@ -34,7 +34,7 @@ import {
type WorkspaceModelAvailabilitySettings,
} from 'src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util';
export interface RegisteredAIModel {
export interface RegisteredAiModel {
modelId: string;
sdkPackage: AiSdkPackage;
model: LanguageModel;
@@ -46,8 +46,8 @@ export interface RegisteredAIModel {
@Injectable()
export class AiModelRegistryService {
private readonly logger = new Logger(AiModelRegistryService.name);
private modelRegistry: Map<string, RegisteredAIModel> = new Map();
private modelConfigCache: Map<string, AIModelConfig> = new Map();
private modelRegistry: Map<string, RegisteredAiModel> = new Map();
private modelConfigCache: Map<string, AiModelConfig> = new Map();
private providerModelDefCache: Map<
string,
{ providerName: string; modelDef: AiProviderModelConfig }
@@ -112,7 +112,7 @@ export class AiModelRegistryService {
this.modelConfigCache.set(
compositeId,
this.toAIModelConfig(compositeId, config, modelDef),
this.toAiModelConfig(compositeId, config, modelDef),
);
this.providerModelDefCache.set(compositeId, {
@@ -134,11 +134,11 @@ export class AiModelRegistryService {
}
}
private toAIModelConfig(
private toAiModelConfig(
compositeId: string,
providerConfig: AiProviderConfig,
modelDef: AiProviderModelConfig,
): AIModelConfig {
): AiModelConfig {
return {
modelId: compositeId,
label: modelDef.label,
@@ -163,19 +163,19 @@ export class AiModelRegistryService {
};
}
getModel(modelId: string): RegisteredAIModel | undefined {
getModel(modelId: string): RegisteredAiModel | undefined {
this.ensureFresh();
return this.modelRegistry.get(modelId);
}
getAvailableModels(): RegisteredAIModel[] {
getAvailableModels(): RegisteredAiModel[] {
this.ensureFresh();
return Array.from(this.modelRegistry.values());
}
getModelConfig(modelId: string): AIModelConfig | undefined {
getModelConfig(modelId: string): AiModelConfig | undefined {
this.ensureFresh();
return this.modelConfigCache.get(modelId);
@@ -187,7 +187,7 @@ export class AiModelRegistryService {
private getFirstAvailableModelFromList(
modelIds: string[],
): RegisteredAIModel | undefined {
): RegisteredAiModel | undefined {
for (const modelId of modelIds) {
const model = this.getModel(modelId);
@@ -199,15 +199,15 @@ export class AiModelRegistryService {
return undefined;
}
getDefaultSpeedModel(): RegisteredAIModel {
getDefaultSpeedModel(): RegisteredAiModel {
return this.getDefaultModelForRole(AiModelRole.FAST);
}
getDefaultPerformanceModel(): RegisteredAIModel {
getDefaultPerformanceModel(): RegisteredAiModel {
return this.getDefaultModelForRole(AiModelRole.SMART);
}
private getDefaultModelForRole(role: AiModelRole): RegisteredAIModel {
private getDefaultModelForRole(role: AiModelRole): RegisteredAiModel {
const prefs = this.preferencesService.getPreferences();
const preferenceKey =
role === AiModelRole.FAST ? 'defaultFastModels' : 'defaultSmartModels';
@@ -228,7 +228,7 @@ export class AiModelRegistryService {
return model;
}
getEffectiveModelConfig(modelId: string): AIModelConfig {
getEffectiveModelConfig(modelId: string): AiModelConfig {
this.ensureFresh();
if (isAutoSelectModelId(modelId)) {
@@ -262,8 +262,8 @@ export class AiModelRegistryService {
}
private createDefaultConfigForCustomModel(
registeredModel: RegisteredAIModel,
): AIModelConfig {
registeredModel: RegisteredAiModel,
): AiModelConfig {
return {
modelId: registeredModel.modelId,
label: registeredModel.modelId,
@@ -316,14 +316,14 @@ export class AiModelRegistryService {
}
}
getAdminFilteredModels(): RegisteredAIModel[] {
getAdminFilteredModels(): RegisteredAiModel[] {
return this.getAvailableModels().filter((model) =>
this.isModelAdminAllowed(model.modelId),
);
}
getAllModelsWithStatus(): Array<{
modelConfig: AIModelConfig;
modelConfig: AiModelConfig;
isAvailable: boolean;
isAdminEnabled: boolean;
isRecommended: boolean;
@@ -401,7 +401,7 @@ export class AiModelRegistryService {
return this.providerConfigService.getCatalogProviderNames();
}
resolveModelForAgent(agent: { modelId: string } | null): RegisteredAIModel {
resolveModelForAgent(agent: { modelId: string } | null): RegisteredAiModel {
const aiModel = this.getEffectiveModelConfig(
agent?.modelId ?? AUTO_SELECT_SMART_MODEL_ID,
);
@@ -106,7 +106,7 @@ export class SdkProviderFactoryService {
case AI_SDK_BEDROCK:
return this.buildBedrockProvider(config);
case AI_SDK_OPENAI_COMPATIBLE:
return this.buildOpenAICompatibleProvider(config);
return this.buildOpenAiCompatibleProvider(config);
default:
throw new Error(`Unsupported SDK package: ${config.npm}`);
}
@@ -167,7 +167,7 @@ export class SdkProviderFactoryService {
};
}
private buildOpenAICompatibleProvider(
private buildOpenAiCompatibleProvider(
config: AiProviderConfig,
): AiSdkProviderInstance {
if (!config.baseUrl) {
@@ -2,8 +2,7 @@ import { type AiSdkPackage, type DataResidency } from 'twenty-shared/ai';
import { type LongContextCost } from 'src/engine/metadata-modules/ai/ai-models/types/long-context-cost.type';
import { type ModelFamily } from 'src/engine/metadata-modules/ai/ai-models/types/model-family.enum';
// TODO: rename to AiModelConfig for consistency with service naming (AiModelRegistryService, etc.)
export type AIModelConfig = {
export type AiModelConfig = {
// Composite model id (`provider/modelName`) used in the registry and GraphQL; same shape as SDK routing when applicable.
modelId: string;
sdkPackage: AiSdkPackage;