927bdd7a21
The model packet read chart.modules.dasha_boundaries, a key the engine never wrote, so no answer ever had sub-period boundaries while the receipt still reported precise timing as allowed. The server now cuts the running mahadasha into antardashas out of the periods the packet already shows, exposes them as their own evidence section, and precise timing requires that section. A run whose calculation succeeded and whose model then wrote nothing was answered with a fixed apology and billed as completed. It now asks once more against the cached calculation, and fails with empty_answer—no charge—if that attempt is silent too. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
6.7 KiB
TypeScript
104 lines
6.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import { completeUsage } from "../src/lib/consultation-billing.ts";
|
|
|
|
const root = new URL("../", import.meta.url);
|
|
const rectificationRoute = readFileSync(new URL("src/app/api/rectification/agent/route.ts", root), "utf8");
|
|
const consultRoute = readFileSync(new URL("src/app/api/consult/route.ts", root), "utf8");
|
|
const packagesRoute = readFileSync(new URL("src/app/api/admin/packages/route.ts", root), "utf8");
|
|
|
|
function sourceBetween(source: string, start: string, end: string): string {
|
|
const startIndex = source.indexOf(start);
|
|
const endIndex = source.indexOf(end, startIndex + start.length);
|
|
assert.ok(startIndex >= 0, `missing start marker: ${start}`);
|
|
assert.ok(endIndex > startIndex, `missing end marker: ${end}`);
|
|
return source.slice(startIndex, endIndex);
|
|
}
|
|
|
|
test("Agentic rectification reuses one case-level usage authorization and the session-pinned model version", () => {
|
|
assert.match(rectificationRoute, /select\("id,messages,session_type,model_id,model_config_version,agentic_rectification_case_id"\)/);
|
|
assert.match(rectificationRoute, /resolveSessionLanguageModel\(\s*chatSession\.model_id,\s*chatSession\.model_config_version,?\s*\)/);
|
|
assert.match(rectificationRoute, /modelConfigVersion: selectedModel\.configVersion/);
|
|
assert.doesNotMatch(rectificationRoute, /loadLanguageModelCatalog|resolveLanguageModelFromCatalog|\bresolveLanguageModel\(|\bdefaultLanguageModel\(/);
|
|
assert.match(rectificationRoute, /const billingRequestPrefix = `rectification:case:\$\{caseId\}`/);
|
|
assert.match(rectificationRoute, /from\("usage_reservations"\)[\s\S]*\.eq\("feature_key", "rectification"\)[\s\S]*\.like\("request_id", `\$\{billingRequestPrefix\}%`\)/);
|
|
assert.match(rectificationRoute, /return reservations.length === 0[\s\S]*`\$\{billingRequestPrefix\}:retry:\$\{reservations.length\}`/);
|
|
assert.match(rectificationRoute, /authorizeUsage\(accounting, \{[\s\S]*requestId: billingRequestId/);
|
|
assert.match(rectificationRoute, /completeUsage\(accounting, userId, billingRequestId,/);
|
|
assert.match(rectificationRoute, /releaseUsage\(accounting, userId, billingRequestId,/);
|
|
});
|
|
|
|
test("free Agentic rectification turns bypass reservation, completion, and cancellation settlement", () => {
|
|
const reserveBilling = sourceBetween(rectificationRoute, "async reserve() {", "async complete(usage) {");
|
|
const completeBilling = sourceBetween(rectificationRoute, "async complete(usage) {", "async release() {");
|
|
const releaseBilling = sourceBetween(rectificationRoute, "async release() {", " };\n\n try {");
|
|
|
|
assert.match(rectificationRoute, /action: z\.enum\(\["opening", "message", "read_only"\]\)/);
|
|
assert.equal(rectificationRoute.match(/if \(action !== "message"\)/g)?.length, 3);
|
|
assert.match(reserveBilling, /if \(action !== "message"\) \{[\s\S]*return \{ success: true, status: 200 \};/);
|
|
assert.match(reserveBilling, /authorizeUsage\(/);
|
|
assert.match(completeBilling, /if \(action !== "message"\) return true;/);
|
|
assert.match(completeBilling, /completeUsage\(/);
|
|
assert.match(releaseBilling, /if \(action !== "message"\) return true;/);
|
|
assert.match(releaseBilling, /releaseUsage\(/);
|
|
});
|
|
|
|
|
|
test("standard consultation resolves and settles the session-pinned model version", () => {
|
|
assert.match(consultRoute, /sessionId: z\.string\(\)\.uuid\(\)/);
|
|
assert.match(consultRoute, /select\("id,model_id,model_config_version,session_type"\)/);
|
|
assert.match(consultRoute, /resolveSessionLanguageModel\(\s*chatSession\.model_id,\s*chatSession\.model_config_version,?\s*\)/);
|
|
assert.match(consultRoute, /actualModelId: selectedModel\.id/);
|
|
assert.match(consultRoute, /modelConfigVersion: selectedModel\.configVersion/);
|
|
});
|
|
|
|
test("standard consultation awaits real usage before durable response settlement", () => {
|
|
assert.doesNotMatch(consultRoute, /recordActualUsage|void usage\.then/);
|
|
assert.doesNotMatch(consultRoute, /inputTokens: 0,[\s\S]*outputTokens: 0,[\s\S]*costMicrousd: 0/);
|
|
assert.match(consultRoute, /async function usagePayload\(usage: Promise<\{ inputTokens\?: number; outputTokens\?: number \}>\)/);
|
|
assert.match(consultRoute, /const resolved = await usage;/);
|
|
assert.match(consultRoute, /const actualUsage = await usagePayload\(usage\);[\s\S]*p_actual_usage: actualUsage/);
|
|
assert.match(consultRoute, /function mergeUsage\(usages: Promise<Usage>\[\]\): Promise<Usage> \{[\s\S]*Promise\.all\(usages\)/);
|
|
assert.equal(consultRoute.match(/usages\.push\(result\.totalUsage\)/g)?.length, 2);
|
|
// Two agentic paths, each with a contract retry and an answer retry: every one
|
|
// of those four model calls spends tokens and must be metered.
|
|
assert.equal(consultRoute.match(/usages\.push\(retried\.totalUsage\)/g)?.length, 4);
|
|
assert.equal(consultRoute.match(/const retryForAnswer = async \(\) => \{/g)?.length, 2);
|
|
});
|
|
|
|
test("standard consultation forwards its stable reservation request as the usage event key", async () => {
|
|
const eventKey = "00000000-0000-4000-8000-000000000002";
|
|
const calls: Record<string, unknown>[] = [];
|
|
const accounting = {
|
|
async rpc(_rpcName: string, args: Record<string, unknown>) {
|
|
calls.push(args);
|
|
return {
|
|
data: { success: true, reservation_id: "00000000-0000-4000-8000-000000000003", credits: 9, error_code: null },
|
|
error: null,
|
|
};
|
|
},
|
|
};
|
|
const usage = { eventKey, actualModelId: "model", inputTokens: 1, outputTokens: 2, costMicrousd: 3, durationMs: 4 };
|
|
|
|
await completeUsage(accounting, "00000000-0000-4000-8000-000000000001", eventKey, usage);
|
|
await completeUsage(accounting, "00000000-0000-4000-8000-000000000001", eventKey, usage);
|
|
|
|
assert.deepEqual(calls.map((call) => (call.p_actual_usage as { eventKey: string }).eventKey), [eventKey, eventKey]);
|
|
assert.match(consultRoute, /eventKey: requestId,/);
|
|
assert.match(consultRoute, /const actualUsage = await usagePayload\(usage\);[\s\S]*p_actual_usage: actualUsage/);
|
|
assert.doesNotMatch(consultRoute, /eventKey:\s*(?:globalThis\.)?crypto\.randomUUID\(\)/);
|
|
});
|
|
|
|
test("legacy admin packages cannot mutate detached payment_packages", () => {
|
|
assert.doesNotMatch(packagesRoute, /payment_packages|queryAdminRows|insert into|update public/);
|
|
assert.match(packagesRoute, /export function GET\(request: Request\)/);
|
|
assert.match(packagesRoute, /const replacement = "\/api\/admin\/products"/);
|
|
assert.match(packagesRoute, /NextResponse\.redirect\(new URL\(replacement, request\.url\), 308\)/);
|
|
for (const method of ["POST", "PATCH", "DELETE"]) {
|
|
assert.match(packagesRoute, new RegExp(`export function ${method}\\(\\)`));
|
|
}
|
|
assert.match(packagesRoute, /status: 410/);
|
|
assert.match(packagesRoute, /error: "旧套餐写接口已停用,请使用统一商品管理。",[\s\S]*replacement,/);
|
|
});
|