Expose enterprise video pricing config
This commit is contained in:
+2
-1
@@ -15,9 +15,10 @@
|
||||
"import-config": "node src/cli/importConfig.js",
|
||||
"init-pools": "node src/cli/initPools.js",
|
||||
"test:community-routes": "node scripts/communityRouteContract.test.js",
|
||||
"test:enterprise-video-pricing": "node scripts/enterpriseVideoPricingContract.test.js",
|
||||
"test:key-manager": "node scripts/keyManagerReleaseContract.test.js",
|
||||
"test:provider-poll-limiter": "node scripts/providerPollLimiterContract.test.js",
|
||||
"test": "npm run test:community-routes && npm run test:key-manager && npm run test:provider-poll-limiter"
|
||||
"test": "npm run test:community-routes && npm run test:enterprise-video-pricing && npm run test:key-manager && npm run test:provider-poll-limiter"
|
||||
},
|
||||
"dependencies": {
|
||||
"alipay-sdk": "^4.14.0",
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
calculateEnterpriseVideoCredits,
|
||||
getEnterpriseVideoCreditRate,
|
||||
getEnterpriseVideoPricingConfig,
|
||||
} = require("../src/enterpriseVideoBilling");
|
||||
|
||||
function getRule(config, id) {
|
||||
const rule = config.rules.find((item) => item.id === id);
|
||||
assert(rule, `missing enterprise video pricing rule: ${id}`);
|
||||
return rule;
|
||||
}
|
||||
|
||||
const config = getEnterpriseVideoPricingConfig();
|
||||
|
||||
assert.equal(config.currency, "CNY");
|
||||
assert.equal(config.creditsPerCny, 100);
|
||||
assert.equal(config.billingUnit, "per_second");
|
||||
assert.deepEqual(config.resolutions, ["720P", "1080P"]);
|
||||
|
||||
assert.equal(getRule(config, "happyhorse").rates["720P"], 0.72);
|
||||
assert.equal(getRule(config, "happyhorse").rates["1080P"], 1.28);
|
||||
assert.equal(getRule(config, "wanxiang-i2v").rates["720P"], 0.6);
|
||||
assert.equal(getRule(config, "kling-muted").rates["1080P"], 0.8);
|
||||
|
||||
assert.equal(
|
||||
getEnterpriseVideoCreditRate({
|
||||
model: "happyhorse-1.0",
|
||||
resolution: "1080P",
|
||||
}),
|
||||
getRule(config, "happyhorse").rates["1080P"],
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
getEnterpriseVideoCreditRate({
|
||||
model: "kling-3.0-dashscope",
|
||||
resolution: "720P",
|
||||
muted: true,
|
||||
hasReferenceVideo: false,
|
||||
}),
|
||||
getRule(config, "kling-muted").rates["720P"],
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
calculateEnterpriseVideoCredits({
|
||||
model: "vidu-q3-turbo",
|
||||
resolution: "1080P",
|
||||
durationSeconds: 5,
|
||||
}),
|
||||
500,
|
||||
);
|
||||
|
||||
console.log("enterprise video pricing contract tests passed");
|
||||
@@ -21,6 +21,53 @@ const ENTERPRISE_VIDEO_ALLOWED_MODELS = new Set([
|
||||
|
||||
const CREDITS_PER_CNY = 100;
|
||||
const CREDIT_UNITS_PER_CREDIT = 100;
|
||||
const ENTERPRISE_VIDEO_RESOLUTIONS = ["720P", "1080P"];
|
||||
const ENTERPRISE_VIDEO_DEFAULT_RESOLUTION = "1080P";
|
||||
|
||||
const ENTERPRISE_VIDEO_PRICING_RULES = [
|
||||
{
|
||||
id: "happyhorse",
|
||||
modelIncludes: ["happyhorse"],
|
||||
rates: { "720P": 0.72, "1080P": 1.28 },
|
||||
},
|
||||
{
|
||||
id: "wanxiang-i2v",
|
||||
modelIncludes: ["wan2.7-i2v", "wanxiang"],
|
||||
rates: { "720P": 0.6, "1080P": 1 },
|
||||
},
|
||||
{
|
||||
id: "wan-animate-s2v",
|
||||
modelIncludes: ["animate-mix", "s2v"],
|
||||
rates: { "720P": 0.6, "1080P": 1 },
|
||||
},
|
||||
{
|
||||
id: "kling-muted-reference",
|
||||
modelIncludes: ["kling"],
|
||||
when: { muted: true, hasReferenceVideo: true },
|
||||
rates: { "720P": 0.9, "1080P": 1.2 },
|
||||
},
|
||||
{
|
||||
id: "kling-muted",
|
||||
modelIncludes: ["kling"],
|
||||
when: { muted: true, hasReferenceVideo: false },
|
||||
rates: { "720P": 0.6, "1080P": 0.8 },
|
||||
},
|
||||
{
|
||||
id: "kling-default",
|
||||
modelIncludes: ["kling"],
|
||||
rates: { "720P": 0.9, "1080P": 1.2 },
|
||||
},
|
||||
{
|
||||
id: "vidu",
|
||||
modelIncludes: ["vidu"],
|
||||
rates: { "720P": 0.6, "1080P": 1 },
|
||||
},
|
||||
{
|
||||
id: "pixverse",
|
||||
modelIncludes: ["pixverse"],
|
||||
rates: { "720P": 0.6, "1080P": 1 },
|
||||
},
|
||||
];
|
||||
|
||||
function normalizeModel(value) {
|
||||
return String(value || "").trim().toLowerCase();
|
||||
@@ -36,6 +83,21 @@ function normalizeEnterpriseVideoDuration(value) {
|
||||
return Math.max(1, Math.ceil(numeric));
|
||||
}
|
||||
|
||||
function enterpriseVideoPricingRuleMatches(rule, input, model) {
|
||||
if (!rule.modelIncludes.some((pattern) => model.includes(pattern))) return false;
|
||||
if (!rule.when) return true;
|
||||
if (Object.prototype.hasOwnProperty.call(rule.when, "muted") && Boolean(input.muted) !== rule.when.muted) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(rule.when, "hasReferenceVideo") &&
|
||||
Boolean(input.hasReferenceVideo) !== rule.when.hasReferenceVideo
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isEnterpriseVideoBillingUser(user) {
|
||||
return Boolean(user?.enterpriseId);
|
||||
}
|
||||
@@ -69,33 +131,10 @@ function getEnterpriseVideoCreditRate(input) {
|
||||
const resolution = normalizeEnterpriseVideoResolution(input.resolution || input.quality);
|
||||
const model = normalizeModel(input.model || input.requestedModel);
|
||||
|
||||
if (model.includes("happyhorse")) {
|
||||
return resolution === "720P" ? 0.72 : 1.28;
|
||||
}
|
||||
|
||||
if (model.includes("wan2.7-i2v") || model.includes("wanxiang")) {
|
||||
return resolution === "720P" ? 0.6 : 1;
|
||||
}
|
||||
|
||||
if (model.includes("animate-mix") || model.includes("s2v")) {
|
||||
return resolution === "720P" ? 0.6 : 1;
|
||||
}
|
||||
|
||||
if (model.includes("kling")) {
|
||||
if (input.muted) {
|
||||
if (input.hasReferenceVideo) return resolution === "720P" ? 0.9 : 1.2;
|
||||
return resolution === "720P" ? 0.6 : 0.8;
|
||||
}
|
||||
return resolution === "720P" ? 0.9 : 1.2;
|
||||
}
|
||||
|
||||
if (model.includes("vidu")) {
|
||||
return resolution === "720P" ? 0.6 : 1.0;
|
||||
}
|
||||
|
||||
if (model.includes("pixverse")) {
|
||||
return resolution === "720P" ? 0.6 : 1.0;
|
||||
}
|
||||
const rule = ENTERPRISE_VIDEO_PRICING_RULES.find((candidate) =>
|
||||
enterpriseVideoPricingRuleMatches(candidate, input, model),
|
||||
);
|
||||
if (rule) return rule.rates[resolution] ?? rule.rates[ENTERPRISE_VIDEO_DEFAULT_RESOLUTION];
|
||||
|
||||
const error = new Error(`Unsupported enterprise video model: ${input.model || input.requestedModel}`);
|
||||
error.status = 403;
|
||||
@@ -103,6 +142,22 @@ function getEnterpriseVideoCreditRate(input) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
function getEnterpriseVideoPricingConfig() {
|
||||
return {
|
||||
currency: "CNY",
|
||||
creditsPerCny: CREDITS_PER_CNY,
|
||||
billingUnit: "per_second",
|
||||
defaultResolution: ENTERPRISE_VIDEO_DEFAULT_RESOLUTION,
|
||||
resolutions: [...ENTERPRISE_VIDEO_RESOLUTIONS],
|
||||
rules: ENTERPRISE_VIDEO_PRICING_RULES.map((rule) => ({
|
||||
id: rule.id,
|
||||
modelIncludes: [...rule.modelIncludes],
|
||||
when: rule.when ? { ...rule.when } : undefined,
|
||||
rates: { ...rule.rates },
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function calculateEnterpriseVideoCredits(input) {
|
||||
const duration = normalizeEnterpriseVideoDuration(input.durationSeconds || input.duration);
|
||||
return Number((getEnterpriseVideoCreditRate(input) * duration * CREDITS_PER_CNY).toFixed(2));
|
||||
@@ -233,6 +288,7 @@ module.exports = {
|
||||
assertEnterpriseVideoModelAllowed,
|
||||
calculateEnterpriseVideoCost,
|
||||
calculateEnterpriseVideoCredits,
|
||||
getEnterpriseVideoPricingConfig,
|
||||
getEnterpriseVideoCreditRate,
|
||||
isEnterpriseVideoBillingUser,
|
||||
isEnterpriseVideoModelAllowed,
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
const { keyManager, listModelPrices, pool } = require("./context");
|
||||
const { getEnterpriseVideoPricingConfig } = require("../enterpriseVideoBilling");
|
||||
|
||||
function registerPriceRoutes(router) {
|
||||
// ── Public ───────────────────────────────────────────────────────────
|
||||
|
||||
router.get("/prices", async (_req, res) => {
|
||||
const prices = await listModelPrices({ enabledOnly: true });
|
||||
res.json(prices);
|
||||
res.json({
|
||||
prices,
|
||||
modelPrices: prices,
|
||||
enterpriseVideoPricing: getEnterpriseVideoPricingConfig(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user