Files
cfw-autumn/shared/utils/agentTypes.ts
2026-02-04 10:48:04 +00:00

312 lines
7.9 KiB
TypeScript

/**
* Agent Types - Types and converters for the AI pricing agent
*
* The "Agent" format is a simplified, AI-friendly format used by the pricing agent.
* It uses string literals like "single_use" instead of enums, making it easier for
* LLMs to generate and for users to read.
*
* This module provides:
* - TypeScript interfaces for the agent format
* - Converters: AgentFeature ↔ Feature, AgentProduct ↔ ProductV2
*/
import {
FeatureType,
FeatureUsageType,
} from "../models/featureModels/featureEnums.js";
import type { Feature } from "../models/featureModels/featureModels.js";
import { AppEnv } from "../models/genModels/genEnums.js";
import { Infinite } from "../models/productModels/productEnums.js";
import type { ProductItem } from "../models/productV2Models/productItemModels/productItemModels.js";
import type { ProductV2 } from "../models/productV2Models/productV2Models.js";
// ============ INTERFACES ============
export type AgentFeatureType =
| "static"
| "boolean"
| "single_use"
| "continuous_use"
| "credit_system";
export interface AgentFeature {
id: string;
name?: string | null;
type: AgentFeatureType;
display?: {
singular: string;
plural: string;
} | null;
credit_schema?: Array<{
metered_feature_id: string;
credit_cost: number;
}> | null;
}
export interface AgentProductItem {
feature_id?: string | null;
included_usage?: number | "inf" | null;
interval?: string | null;
price?: number | null;
tiers?: Array<{ to: number | "inf"; amount: number }> | null;
usage_model?: "prepaid" | "pay_per_use" | null;
billing_units?: number | null;
}
export interface AgentFreeTrial {
length: number;
duration: "day" | "month" | "year";
unique_fingerprint?: boolean;
card_required?: boolean;
}
export interface AgentProduct {
id: string;
name: string;
is_add_on?: boolean;
is_default?: boolean;
group?: string;
items?: AgentProductItem[];
free_trial?: AgentFreeTrial | null;
}
export interface AgentPricingConfig {
features: AgentFeature[];
products: AgentProduct[];
}
// ============ AGENT → SHARED CONVERTERS ============
function mapAgentTypeToFeatureType(agentType: AgentFeatureType): FeatureType {
switch (agentType) {
case "boolean":
case "static":
return FeatureType.Boolean;
case "credit_system":
return FeatureType.CreditSystem;
default:
return FeatureType.Metered;
}
}
function mapAgentTypeToUsageType(
agentType: AgentFeatureType,
): FeatureUsageType | null {
switch (agentType) {
case "single_use":
return FeatureUsageType.Single;
case "continuous_use":
return FeatureUsageType.Continuous;
default:
return null;
}
}
/** Convert AgentFeature → Feature (shared DB type) */
export function agentFeatureToFeature(agentFeature: AgentFeature): Feature {
const usageType = mapAgentTypeToUsageType(agentFeature.type);
const config: Record<string, unknown> = {};
if (usageType) {
config.usage_type = usageType;
}
if (agentFeature.credit_schema) {
config.schema = agentFeature.credit_schema.map((s) => ({
metered_feature_id: s.metered_feature_id,
credit_amount: s.credit_cost,
}));
}
return {
internal_id: agentFeature.id,
org_id: "",
created_at: Date.now(),
env: AppEnv.Sandbox,
id: agentFeature.id,
name: agentFeature.name ?? agentFeature.display?.plural ?? agentFeature.id,
type: mapAgentTypeToFeatureType(agentFeature.type),
config: Object.keys(config).length > 0 ? config : null,
display: agentFeature.display ?? undefined,
archived: false,
event_names: [],
};
}
/** Convert AgentProductItem → ProductItem (shared DB type) */
export function agentItemToProductItem(item: AgentProductItem): ProductItem {
return {
feature_id: item.feature_id ?? undefined,
included_usage:
item.included_usage === "inf"
? Infinite
: (item.included_usage ?? undefined),
interval: item.interval as ProductItem["interval"],
price: item.price ?? undefined,
billing_units: item.billing_units ?? undefined,
usage_model: item.usage_model as ProductItem["usage_model"],
tiers: item.tiers?.map((t) => ({
to: t.to === "inf" ? Infinite : t.to,
amount: t.amount,
})),
};
}
/** Convert AgentProduct → ProductV2 (shared DB type) */
export function agentProductToProductV2(product: AgentProduct): ProductV2 {
return {
internal_id: product.id,
id: product.id,
name: product.name,
description: null,
is_add_on: product.is_add_on ?? false,
is_default: product.is_default ?? false,
version: 1,
group: product.group ?? null,
env: AppEnv.Sandbox,
free_trial: null, // Handled separately in preview transformations
items: (product.items ?? []).map(agentItemToProductItem),
created_at: Date.now(),
};
}
// ============ SHARED → AGENT CONVERTERS ============
function mapFeatureTypeToAgentType(feature: Feature): AgentFeatureType {
if (feature.type === FeatureType.CreditSystem) {
return "credit_system";
}
if (feature.type === FeatureType.Boolean) {
return "boolean";
}
if (feature.type === FeatureType.Metered) {
const usageType = feature.config?.usage_type;
if (
usageType === "continuous_use" ||
usageType === FeatureUsageType.Continuous
) {
return "continuous_use";
}
return "single_use";
}
return "static";
}
/** Convert Feature → AgentFeature */
export function featureToAgentFeature(feature: Feature): AgentFeature {
const agentFeature: AgentFeature = {
id: feature.id,
name: feature.name,
type: mapFeatureTypeToAgentType(feature),
};
if (feature.display?.singular || feature.display?.plural) {
agentFeature.display = {
singular: feature.display.singular ?? feature.name,
plural: feature.display.plural ?? feature.name,
};
}
if (feature.type === FeatureType.CreditSystem && feature.config?.schema) {
agentFeature.credit_schema = feature.config.schema.map(
(s: { metered_feature_id: string; credit_amount: number }) => ({
metered_feature_id: s.metered_feature_id,
credit_cost: s.credit_amount,
}),
);
}
return agentFeature;
}
/** Convert ProductItem → AgentProductItem */
export function productItemToAgentItem(item: ProductItem): AgentProductItem {
const agentItem: AgentProductItem = {};
if (item.feature_id) {
agentItem.feature_id = item.feature_id;
}
if (item.included_usage !== undefined && item.included_usage !== null) {
agentItem.included_usage =
item.included_usage === Infinite ? "inf" : item.included_usage;
}
if (item.interval) {
agentItem.interval = item.interval;
}
if (item.price !== undefined && item.price !== null) {
agentItem.price = item.price;
}
// Copy tiers if present (tiered pricing)
if (item.tiers && item.tiers.length > 0) {
agentItem.tiers = item.tiers.map((t) => ({
to: t.to === Infinite ? "inf" : t.to,
amount: t.amount,
}));
}
if (item.usage_model) {
agentItem.usage_model = item.usage_model as "prepaid" | "pay_per_use";
}
if (item.billing_units) {
agentItem.billing_units = item.billing_units;
}
return agentItem;
}
/** Convert ProductV2 → AgentProduct */
export function productV2ToAgentProduct(product: ProductV2): AgentProduct {
const agentProduct: AgentProduct = {
id: product.id,
name: product.name,
};
if (product.is_add_on) {
agentProduct.is_add_on = true;
}
if (product.is_default) {
agentProduct.is_default = true;
}
if (product.group) {
agentProduct.group = product.group;
}
if (product.items && product.items.length > 0) {
agentProduct.items = product.items.map(productItemToAgentItem);
}
if (product.free_trial) {
agentProduct.free_trial = {
length: product.free_trial.length,
duration: product.free_trial.duration as "day" | "month" | "year",
unique_fingerprint: product.free_trial.unique_fingerprint,
card_required: product.free_trial.card_required,
};
}
return agentProduct;
}
/** Convert ProductV2[] and Feature[] → AgentPricingConfig */
export function convertToAgentConfig({
products,
features,
}: {
products: ProductV2[];
features: Feature[];
}): AgentPricingConfig {
return {
features: features.map(featureToAgentFeature),
products: products.map(productV2ToAgentProduct),
};
}