Expose enterprise video pricing config

This commit is contained in:
2026-06-10 14:27:42 +08:00
parent ca84754bd2
commit 00eba3e209
4 changed files with 145 additions and 29 deletions
+2 -1
View File
@@ -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");
+83 -27
View File
@@ -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,
+6 -1
View File
@@ -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(),
});
});
}