Files
Jyotisha/frontend/src/app/api/rectification/agent/route.ts
T
Jesse_Chen 3a0319694e
Independent Staging Quality Gate / validate (push) Successful in 13m7s
Independent Staging Quality Gate / publish (push) Successful in 29m19s
fix(rectification): keep same-sentence dated events after structured answers
The choice and collect fast paths applied the answer then returned,
so a dated event in the same utterance never reached the evidence
ledger. Idle persist also prechecked follow-up with a hardcoded
collect_evidence outcome instead of the dossier decision.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-29 23:18:07 +08:00

705 lines
29 KiB
TypeScript

import { NextResponse } from "next/server";
import { z } from "zod";
import { getRectificationV9Agent, type RectificationAgentAction } from "@/mastra/agentic-rectification";
import {
evidenceLedgerFingerprint,
loadV9CaseCompute,
loadV9CaseDossier,
persistV9DeterministicTurn,
RectificationToolServiceError,
transitionV9CaseStatus,
} from "@/lib/rectification-agentic/v9/tool-service";
import { decideFromDossier, rectificationFollowupCatalog } from "@/lib/rectification-agentic/v9/decision-from-dossier";
import { applyRectificationChoice, applyCollectFocusDenial, persistNextInterviewIfIdle } from "@/lib/rectification-agentic/v9/answer-choice";
import { mapRectificationRpcError } from "@/lib/rectification-agentic/v9/case-service";
import { CHOICE_ACTION, STOP_ACTION } from "@/lib/rectification-agentic/v9/choice-action";
import { runV9AgentTurn, type V9RunBilling } from "@/lib/rectification-agentic/v9/agent-run";
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 { loadRuntimeFeatureFlags } from "@/lib/feature-flags";
import { isProductEnabled } from "@/lib/product-access";
import { resolveSessionLanguageModel } from "@/lib/model-catalog";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { createServerSupabaseClient } from "@/lib/supabase/server";
import { defaultMessageOrigin, isRectificationMessageOrigin } from "@/lib/rectification-agentic/v9/message-origin";
import { previousInferenceFromReceipt } from "@/lib/rectification-agentic/v9/inference-adapter";
import { parseAgentChoiceCopy } from "@/lib/rectification-agentic/v9/choice-card";
import {
classifyRectificationTurnIntent,
optionIdForAnswerClass,
shouldDeclineCollectFocus,
shouldContinueAgentForDatedEvent,
} from "@/lib/rectification-agentic/v9/turn-intent-classifier";
import { persistServerOwnedFocus, openQuestionFromPersistedFocus, isCollectFocusSchema } from "@/lib/rectification-agentic/v9/server-focus";
import { buildMethodFollowupPlan } from "@/lib/rectification-agentic/v9/method-followup";
export const runtime = "nodejs";
export const maxDuration = 240;
export const DISCRIMINATOR_EXHAUSTED_NARRATION = "当前几个候选已经构成可信区间。你可以再说一件记得住时间的经历,也可以先按这个区间看盘。";
function completedMessageResponse(text: string, requestId: string, caseId: string) {
const body = [
JSON.stringify({ type: "answer.delta", text }),
JSON.stringify({ type: "run.completed" }),
"",
].join("\n");
return new Response(body, {
headers: {
"cache-control": "no-cache, no-transform",
"content-type": "application/x-ndjson; charset=utf-8",
"x-accel-buffering": "no",
"x-ayanam-request-id": requestId,
"x-rectification-case-id": caseId,
},
});
}
const agentRequestSchema = z.object({
caseId: z.string().uuid(),
sessionId: z.string().uuid(),
requestId: z.string().uuid(),
action: z.enum(["opening", "message", "read_only", "answer_choice", "stop_and_review"]),
message: z.string().trim().min(1).max(4000).optional(),
modelId: z.string().trim().min(1).max(64).optional(),
actionId: z.string().uuid().optional(),
focusId: z.string().uuid().optional(),
questionId: z.string().trim().min(1).max(200).optional(),
probeId: z.string().trim().min(1).max(200).nullable().optional(),
optionId: z.enum(["A", "B", "C", "D"]).optional(),
expectedRevision: z.number().int().min(0).max(10_000).optional(),
origin: z.enum([
"typed",
"suggestion_click",
"choice_click",
"voice_input",
"retry_replay",
"system_recovery",
]).optional(),
clientActionId: z.string().uuid().optional(),
}).strict();
function actionToBudget(action: "opening" | "message" | "read_only"): RectificationAgentAction {
if (action === "opening") return "opening";
if (action === "read_only") return "read_only";
return "evidence";
}
async function rectificationBillingRequestId(
accounting: Awaited<ReturnType<typeof createAdminSupabaseClient>>,
userId: string,
caseId: string,
) {
const billingRequestPrefix = `rectification:case:${caseId}`;
const { data, error } = await accounting
.from("usage_reservations")
.select("request_id,status")
.eq("user_id", userId)
.eq("feature_key", "rectification")
.like("request_id", `${billingRequestPrefix}%`);
if (error) throw new Error("RectificationBillingLookupError");
const reservations = (data ?? []) as Array<{ request_id: string; status: string }>;
const active = reservations.find((item) => item.status === "completed")
?? reservations.find((item) => item.status === "reserved");
if (active) return active.request_id;
return reservations.length === 0
? billingRequestPrefix
: `${billingRequestPrefix}:retry:${reservations.length}`;
}
/**
* POST /api/rectification/agent
*
* Case-ref API. The browser sends only caseId/sessionId/requestId/action/
* message; the server verifies the exact Case↔Session binding, reads the
* durable dossier, streams the agent's fullStream and emits allowlisted NDJSON
* phases only. Client history can never override server history.
*/
export async function POST(request: Request) {
let supabase;
let accounting;
try {
supabase = await createServerSupabaseClient();
accounting = createAdminSupabaseClient();
} catch {
return NextResponse.json(
{ error: "服务尚未配置", message: "请先配置数据库环境变量。" },
{ status: 503 },
);
}
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
if (!await isProductEnabled("rectification")) {
return NextResponse.json(
{ error: "生时校正服务暂未开放", code: "rectification_product_disabled" },
{ status: 503 },
);
}
const parsed = agentRequestSchema.safeParse(await request.json().catch(() => null));
if (!parsed.success) {
return NextResponse.json(
{ error: "请求格式不正确", details: parsed.error.flatten() },
{ status: 400 },
);
}
const promptSource = [
parsed.data.action === "message" ? parsed.data.message ?? "" : "",
].join("\n");
if (blocksPromptExtraction(promptSource)) {
return NextResponse.json(
{ error: "无法处理该请求", message: "我不能提供系统提示词、技能原文或任何密钥。你可以继续描述人生事件。" },
{ status: 400 },
);
}
const userId = user.id;
const { caseId, sessionId, requestId, action } = parsed.data;
const isStructuredChoice = action === "answer_choice" || action === "stop_and_review";
// Feature selector: the V9 runtime is DB-driven. When the flag is not
// published/enabled, no new runs are served (legacy stays read-only).
try {
const flags = await loadRuntimeFeatureFlags(["rectification_runtime_version"]);
const runtime = flags.get("rectification_runtime_version");
if (!runtime?.enabled) {
return NextResponse.json(
{ error: "生时校正服务暂未开放", code: "rectification_runtime_disabled" },
{ status: 503 },
);
}
} catch {
// Flag read failure defaults to the V9 runtime being unavailable.
return NextResponse.json(
{ error: "生时校正服务暂未开放", code: "rectification_runtime_disabled" },
{ status: 503 },
);
}
// Read the case projection to verify ownership + session binding + model.
const { data: caseRow, error: caseError } = await accounting.rpc(
"get_agentic_rectification_case",
{ p_user_id: userId, p_case_id: caseId },
);
if (caseError) {
const message = caseError.message ?? "";
if (message.includes("agentic_rectification_case_not_found")) {
return NextResponse.json({ error: "校正记录不存在或无权访问", code: "case_not_found" }, { status: 404 });
}
return NextResponse.json({ error: "暂时无法读取校正记录", code: "rectification_service_failed" }, { status: 503 });
}
const caseView = Array.isArray(caseRow) ? caseRow[0] : caseRow;
const boundSessionId = caseView && typeof caseView === "object"
? (caseView as { session_id?: unknown }).session_id
: null;
if (typeof boundSessionId !== "string" || boundSessionId !== sessionId) {
return NextResponse.json(
{ error: "校正记录与会话绑定不一致", code: "case_session_mismatch" },
{ status: 409 },
);
}
const caseStatus = caseView && typeof caseView === "object"
? String((caseView as { status?: unknown }).status ?? "")
: "";
const skillVersion = caseView && typeof caseView === "object"
? String((caseView as { skill_version?: unknown }).skill_version ?? "")
: "";
if (caseStatus === "confirmed" || caseStatus === "closed"
|| caseStatus === "abandoned" || caseStatus === "superseded") {
return NextResponse.json(
{ error: "该校正已结束,只能查看历史", code: "case_terminal" },
{ status: 409 },
);
}
const { data: chatSession, error: chatSessionError } = await supabase
.from("chat_sessions")
.select("id,messages,session_type,model_id,model_config_version,agentic_rectification_case_id")
.eq("id", sessionId)
.eq("user_id", userId)
.maybeSingle();
if (chatSessionError) {
return NextResponse.json({ error: "暂时无法读取会话", message: "请稍后重试。" }, { status: 503 });
}
if (!chatSession || chatSession.session_type !== "birth_time_rectification") {
return NextResponse.json({ error: "生时校正会话不存在", message: "请重新进入生时校正。" }, { status: 404 });
}
if (chatSession.agentic_rectification_case_id !== caseId) {
return NextResponse.json(
{ error: "校正记录与会话绑定不一致", code: "case_session_mismatch" },
{ status: 409 },
);
}
if (isStructuredChoice) {
const actionId = parsed.data.actionId;
const focusId = parsed.data.focusId;
const expectedRevision = parsed.data.expectedRevision;
if (!actionId || !focusId || expectedRevision === undefined) {
return NextResponse.json(
{ error: "选择题请求缺少 actionId、focusId 或 expectedRevision" },
{ status: 400 },
);
}
if (action === "answer_choice" && !parsed.data.optionId) {
return NextResponse.json({ error: "选择题请求缺少 optionId" }, { status: 400 });
}
try {
const applied = await applyRectificationChoice(accounting, {
userId,
caseId,
sessionId,
actionId,
action: action === "stop_and_review" ? STOP_ACTION : CHOICE_ACTION,
focusId,
questionId: parsed.data.questionId,
probeId: parsed.data.probeId ?? null,
optionId: action === "stop_and_review" ? "stop" : parsed.data.optionId!,
expectedRevision,
});
if (action === "stop_and_review") {
await transitionV9CaseStatus(accounting, userId, caseId, "paused");
}
return NextResponse.json({
type: "choice.applied",
action: applied.idempotent ? "replayed" : "applied",
replayed: applied.idempotent,
status: applied.status,
narrationPersisted: applied.narrationPersisted,
focusId: applied.focusId,
questionId: applied.questionId,
optionId: applied.optionId,
probeId: applied.probeId,
caseRevision: applied.revision,
narration: applied.narration,
userMessage: applied.userDisplay,
sourceQuote: applied.sourceQuote,
derivedContext: applied.derivedContext,
nextAction: applied.nextAction,
nextInterviewPersisted: applied.nextInterviewPersisted,
nextChoiceReady: applied.nextChoiceReady,
});
} catch (error) {
if (error instanceof RectificationToolServiceError) {
const mapped = mapRectificationRpcError(error);
return NextResponse.json(
{ error: mapped.message, message: mapped.message, code: mapped.code },
{ status: mapped.status },
);
}
return NextResponse.json(
{ error: "选择题处理失败", message: "请稍后重试。" },
{ status: 500 },
);
}
}
const selectedModel = await resolveSessionLanguageModel(
chatSession.model_id,
chatSession.model_config_version,
);
if (!selectedModel) {
return NextResponse.json(
{ error: "模型暂不可用", message: "请选择其他模型后重新发送,本次不会扣除点数。" },
{ status: 409 },
);
}
if (action === "message") {
try {
const dossier = await loadV9CaseDossier(accounting, userId, caseId);
const focus = dossier.conversationSummary.activeFocus;
const choice = focus ? parseAgentChoiceCopy(focus.expectedAnswerSchema) : null;
if (focus && choice) {
let classified = null;
try {
classified = await classifyRectificationTurnIntent(selectedModel, {
focus,
userMessage: parsed.data.message ?? "",
caseStatus,
signal: request.signal,
});
} catch {
classified = null;
}
if (!classified || classified.intent === "unclear") {
const narration = "我没能确定这句话是否在回答当前问题。请点选下面的选项,或换一种说法。";
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
assistantMessage: narration,
});
return completedMessageResponse(narration, requestId, caseId);
}
if (classified.intent === "answer_current_focus") {
if (!classified.answer_class) {
return completedMessageResponse("当前问题已更新,请刷新后重新作答。", requestId, caseId);
}
const optionId = optionIdForAnswerClass(focus, classified.answer_class);
if (!optionId) {
return completedMessageResponse("当前问题已更新,请刷新后重新作答。", requestId, caseId);
}
const continueToAgent = shouldContinueAgentForDatedEvent(classified);
const previous = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
const applied = await applyRectificationChoice(accounting, {
userId,
caseId,
sessionId,
actionId: requestId,
action: CHOICE_ACTION,
focusId: focus.id,
questionId: focus.questionId,
probeId: typeof focus.expectedAnswerSchema.probe_id === "string"
? focus.expectedAnswerSchema.probe_id
: null,
optionId,
expectedRevision: previous?.revision ?? 0,
userDisplay: parsed.data.message ?? null,
deferFollowup: continueToAgent,
});
if (!continueToAgent) {
return completedMessageResponse(applied.narration, requestId, caseId);
}
}
if (classified.intent === "stop_rectification") {
const previous = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
const applied = await applyRectificationChoice(accounting, {
userId,
caseId,
sessionId,
actionId: requestId,
action: STOP_ACTION,
focusId: focus.id,
questionId: focus.questionId,
probeId: typeof focus.expectedAnswerSchema.probe_id === "string"
? focus.expectedAnswerSchema.probe_id
: null,
optionId: "stop",
expectedRevision: previous?.revision ?? 0,
userDisplay: parsed.data.message ?? null,
});
await transitionV9CaseStatus(accounting, userId, caseId, "paused");
return completedMessageResponse(applied.narration, requestId, caseId);
}
} else if (focus && isCollectFocusSchema(focus.expectedAnswerSchema)) {
let classified = null;
try {
classified = await classifyRectificationTurnIntent(selectedModel, {
focus,
userMessage: parsed.data.message ?? "",
caseStatus,
signal: request.signal,
});
} catch {
classified = null;
}
if (shouldDeclineCollectFocus(classified)) {
const continueToAgent = shouldContinueAgentForDatedEvent(classified);
const applied = await applyCollectFocusDenial(accounting, {
userId,
caseId,
focusId: focus.id,
deferFollowup: continueToAgent,
});
if (!continueToAgent) {
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
assistantMessage: applied.narration,
});
return completedMessageResponse(applied.narration, requestId, caseId);
}
}
} else {
let birthDate: string | null = null;
try {
const compute = await loadV9CaseCompute(accounting, userId, caseId);
birthDate = String(compute.baselineBirthSnapshot.birth_date ?? "") || null;
} catch {
// Ranking stays fail-open when compute is unavailable.
}
const decision = decideFromDossier(dossier, { birthDate });
if (decision.nextAction === "ask_candidate_discriminator") {
const catalog = rectificationFollowupCatalog(
dossier.latestResult,
dossier.evidence,
);
const plan = buildMethodFollowupPlan({
evidence: dossier.evidence,
declinedTopics: dossier.conversationSummary.declinedSkippedTopics,
sessionOutcome: "discriminate_candidates",
...catalog,
birthDate,
candidatesSeparated: false,
});
const persisted = await persistServerOwnedFocus({
accounting,
userId,
caseId,
activeFocus: focus,
decisionReceipt: dossier.latestResult?.decisionReceipt,
followup: plan.next_followup,
});
const open = openQuestionFromPersistedFocus(persisted);
if (open && persisted.focus && open.unrenderable !== true) {
let classified = null;
try {
classified = await classifyRectificationTurnIntent(selectedModel, {
focus: persisted.focus,
userMessage: parsed.data.message ?? "",
caseStatus,
signal: request.signal,
});
} catch {
classified = null;
}
if (classified?.intent === "answer_current_focus" && classified.answer_class) {
const optionId = optionIdForAnswerClass(persisted.focus, classified.answer_class);
if (optionId) {
const previous = previousInferenceFromReceipt(dossier.latestResult?.decisionReceipt ?? null);
const applied = await applyRectificationChoice(accounting, {
userId,
caseId,
sessionId,
actionId: requestId,
action: CHOICE_ACTION,
focusId: persisted.focus.id,
questionId: persisted.focus.questionId,
probeId: typeof persisted.focus.expectedAnswerSchema.probe_id === "string"
? persisted.focus.expectedAnswerSchema.probe_id
: null,
optionId,
expectedRevision: previous?.revision ?? 0,
userDisplay: parsed.data.message ?? null,
});
return completedMessageResponse(applied.narration, requestId, caseId);
}
}
const narration = "请点选下面的选项作答。点选和一句话回答会按同一规则计入当前区分题。";
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
assistantMessage: narration,
});
return completedMessageResponse(narration, requestId, caseId);
}
if (!plan.next_followup) {
await persistV9DeterministicTurn(accounting, userId, caseId, {
requestId,
userMessage: parsed.data.message ?? null,
assistantMessage: DISCRIMINATOR_EXHAUSTED_NARRATION,
});
return completedMessageResponse(DISCRIMINATOR_EXHAUSTED_NARRATION, requestId, caseId);
}
}
}
} catch (error) {
if (error instanceof RectificationToolServiceError) {
const mapped = mapRectificationRpcError(error);
return NextResponse.json(
{ error: mapped.message, message: mapped.message, code: mapped.code },
{ status: mapped.status },
);
}
console.error(`[rectification-v9] focus answer failed case=${caseId} reason=${error instanceof Error ? error.name : "Unknown"}`);
return NextResponse.json(
{ error: "校正服务暂时不可用", message: "请稍后重试。", code: "rectification_service_failed" },
{ status: 500 },
);
}
}
const requestTime = new Date();
const chinaTime = new Date(requestTime.getTime() + 8 * 60 * 60 * 1000)
.toISOString()
.replace("T", " ")
.slice(0, 19);
const timeContext = `服务端当前时间(权威):${requestTime.toISOString()};中国标准时间(UTC+8):${chinaTime}。涉及“现在、今天、今年、未来几个月”等相对时间时,以此为准。`;
const encoder = new TextEncoder();
const body = new ReadableStream<Uint8Array>({
async start(controller) {
let closed = false;
const send = (event: Record<string, unknown>) => {
if (closed) return;
const safe = safePublicEvent(event);
if (!safe) return;
controller.enqueue(encoder.encode(`${JSON.stringify(safe)}\n`));
};
const billing: V9RunBilling = {
async reserve() {
// opening / read-only turns are free; the first substantive run
// reserves once, and resume/retry reuse the same case-bound request.
if (action !== "message") {
return { success: true, status: 200 };
}
try {
const billingRequestId = await rectificationBillingRequestId(accounting, userId, caseId);
const result = await authorizeUsage(accounting, {
userId,
requestId: billingRequestId,
featureKey: "rectification",
requestedModelId: selectedModel.id,
creditCost: selectedModel.creditCost,
});
if (result.success) return { success: true, status: 200 };
return {
success: false,
reason: result.reason ?? "billing_denied",
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 };
}
},
async complete(usage) {
if (action !== "message") return true;
try {
const billingRequestId = await rectificationBillingRequestId(accounting, userId, caseId);
const settlement = await completeUsage(accounting, userId, billingRequestId, {
eventKey: requestId,
actualModelId: selectedModel.id,
modelConfigVersion: selectedModel.configVersion,
inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens,
costMicrousd: Math.round((
usage.inputTokens * (selectedModel.inputCostMicrousdPerMillion ?? 0)
+ usage.outputTokens * (selectedModel.outputCostMicrousdPerMillion ?? 0)
) / 1_000_000),
durationMs: usage.durationMs,
});
return settlement.success;
} catch (error) {
console.warn(`[rectification-v9] usage settle failed case=${caseId} reason=${error instanceof Error ? error.name : "Unknown"}`);
return false;
}
},
async release() {
if (action !== "message") return true;
try {
const billingRequestId = await rectificationBillingRequestId(accounting, userId, caseId);
const settlement = await releaseUsage(accounting, userId, billingRequestId, "rectification_cancelled");
return settlement.success;
} catch {
return false;
}
},
};
try {
const result = await runV9AgentTurn({
userId,
caseId,
sessionId,
requestId,
action: actionToBudget(action),
message: action === "message" ? parsed.data.message ?? "" : null,
messageOrigin: isRectificationMessageOrigin(parsed.data.origin)
? parsed.data.origin
: defaultMessageOrigin(action),
clientActionId: parsed.data.clientActionId ?? requestId,
modelName: selectedModel.id,
skillName: RECTIFICATION_SKILL_NAME,
skillVersion: skillVersion || RECTIFICATION_SKILL_VERSION,
accounting: accounting as never,
billing,
emit: (event) => send(event),
signal: request.signal,
timeContext,
generationModel: selectedModel.model,
buildAgent: async (turnId, skillPackage, attemptId) => {
let decision;
try {
const dossier = await loadV9CaseDossier(accounting as never, userId, caseId);
let birthDate: string | null = null;
try {
const compute = await loadV9CaseCompute(accounting as never, userId, caseId);
birthDate = String(compute.baselineBirthSnapshot.birth_date ?? "") || null;
} catch {
// Ranking stays fail-open when compute is unavailable.
}
decision = decideFromDossier(dossier, {
currentEvidenceFingerprint: evidenceLedgerFingerprint(dossier.evidence),
birthDate,
});
} catch {
decision = undefined;
}
return getRectificationV9Agent(selectedModel, {
userId,
caseId,
turnId,
attemptId,
userMessage: action === "message" ? parsed.data.message ?? null : null,
accounting: accounting as never,
decision,
}, skillPackage);
},
});
if (!result.ok) {
send({ type: "error", message: "生时校正暂时不可用,请稍后重试。" });
} else {
if (action === "message") {
try {
await persistNextInterviewIfIdle({
accounting: accounting as never,
userId,
caseId,
});
} catch (error) {
console.warn(
`[rectification-v9] persist next interview after turn failed case=${caseId} reason=${error instanceof Error ? error.name : "Unknown"}`,
);
}
}
send({ type: "done", emitted: true });
}
} catch (error) {
const code = error instanceof RectificationToolServiceError ? error.code : "run_failed";
console.error(`[rectification-v9] run failed case=${caseId} code=${code}`);
if (code === "billing_denied" || code.includes("insufficient")) {
send({ type: "error", code: "billing_denied", 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")) {
send({ type: "error", code: "skill_identity_missing", message: "该校正绑定的 Skill 版本不可用,请联系支持人员。" });
} else if (code.includes("skill_identity_mismatch")) {
send({ type: "error", code: "skill_identity_mismatch", message: "校正记录与 Skill 版本绑定不一致,请先刷新后重试。" });
} else {
send({ type: "error", code: "run_failed", message: "生时校正暂时不可用,请稍后再试。" });
}
} finally {
closed = true;
try {
controller.close();
} catch {
// already closed
}
}
},
});
return new Response(body, {
headers: {
"cache-control": "no-cache, no-transform",
"content-type": "application/x-ndjson; charset=utf-8",
"x-accel-buffering": "no",
"x-ayanam-request-id": requestId,
"x-rectification-case-id": caseId,
},
});
}