fix(rectification): surface pricing failures and persist opening question
This commit is contained in:
@@ -30,7 +30,7 @@ import {
|
||||
} from "@/lib/consultation-entrypoint";
|
||||
import { CreditRpcError } from "@/lib/consultation-billing";
|
||||
import { cachedSystemMessage, mergePromptCacheUsage, promptCacheUsage } from "@/lib/agent-generation-settings";
|
||||
import { resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { FeaturePricingError, resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { reserveConsultationModel } from "@/lib/consultation-model-selection";
|
||||
import { resolveSessionLanguageModel } from "@/lib/model-catalog";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
@@ -393,6 +393,19 @@ export async function POST(request: Request) {
|
||||
),
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof FeaturePricingError) {
|
||||
console.error(
|
||||
`[billing] reservation failed request=${requestId} reason=${error.code}`,
|
||||
);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "计费配置不可用",
|
||||
message: "当前服务的计费配置尚未完成,请联系支持人员,本次不会扣点。",
|
||||
code: "pricing_configuration_unavailable",
|
||||
},
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
if (error instanceof ConsultationPlanValidationError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ import { safePublicEvent } from "@/lib/rectification-agentic/v9/stream-mapping";
|
||||
import { RECTIFICATION_SKILL_NAME, RECTIFICATION_SKILL_VERSION } from "@/lib/rectification-agentic/v9/case-status";
|
||||
import { blocksPromptExtraction } from "@/lib/consult-safety";
|
||||
import { authorizeUsage, completeUsage, releaseUsage } from "@/lib/consultation-billing";
|
||||
import { resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { FeaturePricingError, resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { loadRuntimeFeatureFlags } from "@/lib/feature-flags";
|
||||
import { isProductEnabled } from "@/lib/product-access";
|
||||
import { resolveSessionLanguageModel } from "@/lib/model-catalog";
|
||||
@@ -579,8 +579,9 @@ export async function POST(request: Request) {
|
||||
status: result.reason === "insufficient_credits" ? 402 : 503,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[rectification-v9] reserve failed case=${caseId} reason=${error instanceof Error ? error.name : "Unknown"}`);
|
||||
return { success: false, reason: "billing_unavailable", status: 503 };
|
||||
const reason = error instanceof FeaturePricingError ? error.code : "billing_unavailable";
|
||||
console.error(`[rectification-v9] reserve failed case=${caseId} code=${reason}`);
|
||||
return { success: false, reason, status: 503 };
|
||||
}
|
||||
},
|
||||
async complete(usage) {
|
||||
@@ -672,9 +673,9 @@ export async function POST(request: Request) {
|
||||
if (!result.ok) {
|
||||
send({ type: "error", message: "生时校正暂时不可用,请稍后重试。" });
|
||||
} else {
|
||||
if (action === "message") {
|
||||
if (action === "message" || action === "opening") {
|
||||
try {
|
||||
const idle = await persistNextInterviewIfIdle({
|
||||
await persistNextInterviewIfIdle({
|
||||
accounting: accounting as never,
|
||||
userId,
|
||||
caseId,
|
||||
@@ -685,7 +686,7 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
try {
|
||||
const exit = await ensureNonTerminalTurnExit({
|
||||
await ensureNonTerminalTurnExit({
|
||||
accounting: accounting as never,
|
||||
userId,
|
||||
caseId,
|
||||
@@ -711,6 +712,8 @@ export async function POST(request: Request) {
|
||||
send({ type: "error", code, message: "当前账户触发安全熔断,请稍后再试或联系支持。" });
|
||||
} else if (code === "billing_denied") {
|
||||
send({ type: "error", code, message: "暂时无法确认校正点数,请稍后重试。" });
|
||||
} else if (code.startsWith("feature_pricing_") || code === "billing_unavailable") {
|
||||
send({ type: "error", code: "billing_unavailable", message: "当前服务的计费配置不可用,请联系支持人员。" });
|
||||
} else if (code.includes("legacy_skill_identity_unverifiable")) {
|
||||
send({ type: "error", code: "skill_identity_unverifiable", message: "该校正绑定的是无法核验的历史 Skill,请先采用当前注册版本。" });
|
||||
} else if (code.includes("skill_identity_missing")) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { createSupabasePersonalReportJobService } from "@/lib/personal-report-job-service";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { authorizeUsage, completeUsage, releaseUsage } from "@/lib/consultation-billing";
|
||||
import { resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { FeaturePricingError, resolveFeaturePricing } from "@/lib/feature-pricing";
|
||||
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
|
||||
@@ -138,6 +138,7 @@ export async function POST(request: Request) {
|
||||
|
||||
const catalog = await loadLanguageModelCatalog();
|
||||
const defaultModel = catalog.models.find((entry) => entry.id === catalog.defaultModelId) ?? null;
|
||||
let pricingConfigurationErrorCode: string | null = null;
|
||||
const deps: ReportCreateCoreDeps = {
|
||||
requestUrl: request.url,
|
||||
origin: request.headers.get("origin"),
|
||||
@@ -199,11 +200,19 @@ export async function POST(request: Request) {
|
||||
model: defaultModel,
|
||||
billing: {
|
||||
reserve: async ({ userId: billingUserId, requestId, modelId }) => {
|
||||
const pricing = await resolveFeaturePricing(admin, "report.full", modelId);
|
||||
return authorizeUsage(admin, {
|
||||
userId: billingUserId, requestId, featureKey: "report.full",
|
||||
requestedModelId: modelId, creditCost: pricing.credit_cost,
|
||||
});
|
||||
try {
|
||||
const pricing = await resolveFeaturePricing(admin, "report.full", modelId);
|
||||
return authorizeUsage(admin, {
|
||||
userId: billingUserId, requestId, featureKey: "report.full",
|
||||
requestedModelId: modelId, creditCost: pricing.credit_cost,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof FeaturePricingError) {
|
||||
pricingConfigurationErrorCode = error.code;
|
||||
console.error(`[reports] billing reservation failed request=${requestId} reason=${error.code}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
complete: async ({ userId: billingUserId, requestId, usage }) => {
|
||||
if (!defaultModel) return false;
|
||||
@@ -231,6 +240,16 @@ export async function POST(request: Request) {
|
||||
};
|
||||
|
||||
const response = await resolveReportCreate(deps);
|
||||
if (pricingConfigurationErrorCode) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "计费配置不可用",
|
||||
message: "当前服务的计费配置尚未完成,请联系支持人员,本次不会扣点。",
|
||||
code: "pricing_configuration_unavailable",
|
||||
},
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
if (response.status >= 500 && profileError) {
|
||||
console.error(`[reports] create failed request=${String(deps.rawBody && typeof deps.rawBody === "object"
|
||||
? (deps.rawBody as Record<string, unknown>).requestId ?? "unknown"
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
type RectificationActivityChangedEvent,
|
||||
type RectificationActivityEvent,
|
||||
type RectificationChoiceAppliedEvent,
|
||||
type PublicRectificationActivity,
|
||||
type PublicRectificationMethod,
|
||||
type PublicRectificationPhase,
|
||||
type PublicRectificationTool,
|
||||
@@ -36,6 +35,7 @@ export type PublicPhaseStreamEvent = Readonly<{
|
||||
|
||||
export type PublicErrorCode =
|
||||
| "billing_denied"
|
||||
| "billing_unavailable"
|
||||
| "skill_identity_unverifiable"
|
||||
| "skill_identity_missing"
|
||||
| "skill_identity_mismatch"
|
||||
@@ -284,6 +284,7 @@ export function safePublicEvent(value: unknown): PublicStreamEvent | null {
|
||||
if (event.type === "error") {
|
||||
const codes = new Set<PublicErrorCode>([
|
||||
"billing_denied",
|
||||
"billing_unavailable",
|
||||
"skill_identity_unverifiable",
|
||||
"skill_identity_missing",
|
||||
"skill_identity_mismatch",
|
||||
|
||||
Reference in New Issue
Block a user