feat(pricing/ai): improve billing metered pricing + add pricing on ai chat (#14092)

## TODO:

- [x] display "yearly" or "monthly" wording everywhere it's needed
- [ ] Add button with "downgrade" or "upgrade" to save the change of
credits price + modal to validate
- [x] Add renewal date 
- [ ] Implement
https://docs.stripe.com/billing/subscriptions/subscription-schedules for
`switchFromYearlyToMonthly` and decrease number of credits
This commit is contained in:
Antoine Moreaux
2025-08-29 18:23:07 +02:00
committed by GitHub
parent 7df9094939
commit 1c4568c8b1
45 changed files with 2048 additions and 327 deletions
@@ -7,8 +7,6 @@ import {
type CoreMessage,
type CoreUserMessage,
type FilePart,
generateObject,
generateText,
type ImagePart,
streamText,
ToolSet,
@@ -30,11 +28,10 @@ import { AgentHandoffToolService } from 'src/engine/metadata-modules/agent/agent
import { AGENT_CONFIG } from 'src/engine/metadata-modules/agent/constants/agent-config.const';
import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/agent/constants/agent-system-prompts.const';
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/agent/types/recordIdsByObjectMetadataNameSingular.type';
import { convertOutputSchemaToZod } from 'src/engine/metadata-modules/agent/utils/convert-output-schema-to-zod';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { type OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
import { AIBillingService } from 'src/engine/core-modules/ai/services/ai-billing.service';
import { AgentToolGeneratorService } from './agent-tool-generator.service';
import { AgentEntity } from './agent.entity';
@@ -61,6 +58,7 @@ export class AgentExecutionService {
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly aiModelRegistryService: AiModelRegistryService,
private readonly agentToolGeneratorService: AgentToolGeneratorService,
private readonly aiBillingService: AIBillingService,
@InjectRepository(AgentEntity)
private readonly agentRepository: Repository<AgentEntity>,
@InjectRepository(FileEntity)
@@ -301,66 +299,18 @@ export class AgentExecutionService {
`Sending request to AI model with ${llmMessages.length} messages`,
);
return streamText(aiRequestConfig);
}
const model = await this.aiModelRegistryService.resolveModelForAgent(agent);
async executeAgent({
agent,
schema,
userPrompt,
}: {
agent: AgentEntity | null;
context: Record<string, unknown>;
schema: OutputSchema;
userPrompt: string;
}): Promise<AgentExecutionResult> {
try {
const aiRequestConfig = await this.prepareAIRequestConfig({
system: `You are executing as part of a workflow automation. ${agent ? agent.prompt : ''}`,
agent,
prompt: userPrompt,
});
const textResponse = await generateText(aiRequestConfig);
const stream = streamText(aiRequestConfig);
if (Object.keys(schema).length === 0) {
return {
result: { response: textResponse.text },
usage: textResponse.usage,
};
}
const output = await generateObject({
system: AGENT_SYSTEM_PROMPTS.OUTPUT_GENERATOR,
model: aiRequestConfig.model,
prompt: `Based on the following execution results, generate the structured output according to the schema:
Execution Results: ${textResponse.text}
Please generate the structured output based on the execution results and context above.`,
schema: convertOutputSchemaToZod(schema),
});
return {
result: output.object,
usage: {
promptTokens:
(textResponse.usage?.promptTokens ?? 0) +
(output.usage?.promptTokens ?? 0),
completionTokens:
(textResponse.usage?.completionTokens ?? 0) +
(output.usage?.completionTokens ?? 0),
totalTokens:
(textResponse.usage?.totalTokens ?? 0) +
(output.usage?.totalTokens ?? 0),
},
};
} catch (error) {
if (error instanceof AgentException) {
throw error;
}
throw new AgentException(
error instanceof Error ? error.message : 'Agent execution failed',
AgentExceptionCode.AGENT_EXECUTION_FAILED,
stream.usage.then((usage) => {
this.aiBillingService.calculateAndBillUsage(
model.modelId,
usage,
workspace.id,
);
}
});
return stream;
}
}