mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-10-18 01:20:29 +00:00
Diagnose setup by what is certain, and deliver errors the way the client asked
Review of the branch found the persona diagnosis was mostly truncation wearing a name tag. The <.> marker sits at the start of the system message and the persona marker after the card, so a card Janitor cut short arrives with the prompt present and the persona gone — and was told its persona was wrong, with truncation as an afterthought. The original message was only ever about context size, which is the best evidence there is about what actually happens. diagnoseSetup() now says only what the markers make certain. A missing <.> is a missing prompt, since truncation cannot remove the first three characters. A persona marker that is present but not "." is a wrong description, quoted back so there is nothing to guess. A persona marker that is gone, or cut off mid-tag, leads with truncation and mentions the persona second. The error path decided "streaming?" from the Accept header while the success path used body.stream, so a client that sent stream:true without that header got JSON back for every error and never displayed it. The flag is hoisted above the try; the header is only a fallback for when the body could not be read. Token search is restricted to after the greeting, in order. A card built from a sucker-proxied chat could carry an expired token in its text, and a lookup on it would fail forever. Conversation order also means the token a chat was started with always wins over one pasted later. The system fallback now takes a role-less first message only; a request with roles but no system turn is reported as such rather than misdiagnosed as a missing prompt. The echo feature reads the parser's lastUser instead of its own case-sensitive lookup. "Nothing was saved" is dropped from the unknown-error message, since it is not guaranteed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
This commit is contained in:
+16
-17
@@ -9,7 +9,7 @@ import {
|
||||
NO_CARD,
|
||||
NO_GREETING,
|
||||
TOO_FEW_MESSAGES,
|
||||
missingSetup,
|
||||
diagnoseSetup,
|
||||
userFacingMessage,
|
||||
} from "@/lib/errors";
|
||||
|
||||
@@ -165,12 +165,11 @@ function extractCardData(messages: Message[]): ExtractedCard {
|
||||
`Name extraction: user="${nameFromUser}", inferred="${inferredName}", tracking="${cleanTrackingName}", display="${displayName}"`
|
||||
);
|
||||
|
||||
// Each marker is one of the two REQUIRED steps in the instructions, so the
|
||||
// error can name the one that did not arrive.
|
||||
const hasPrompt = content.includes("<.>");
|
||||
const hasPersona = content.includes("<UserPersona>.</UserPersona>");
|
||||
if (!hasPrompt || !hasPersona) {
|
||||
throw missingSetup(hasPrompt, hasPersona);
|
||||
// The two markers are the two REQUIRED setup steps; the diagnosis names
|
||||
// the one that is wrong, or says the card was cut short.
|
||||
const setupProblem = diagnoseSetup(content);
|
||||
if (setupProblem) {
|
||||
throw setupProblem;
|
||||
}
|
||||
|
||||
content = content.replace("<.>", "");
|
||||
@@ -337,9 +336,12 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
// Decided from the body, and read again in the catch below: an error must
|
||||
// go back the same way a success would, or the client never shows it.
|
||||
let isStreamingRequest = false;
|
||||
try {
|
||||
const body = await request.json();
|
||||
const isStreamingRequest = body.stream === true;
|
||||
isStreamingRequest = body.stream === true;
|
||||
|
||||
if (!body.messages || body.messages.length < 2) {
|
||||
if (isStreamingRequest) {
|
||||
@@ -516,12 +518,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Echo back anything the user sent after an "ECHO:" marker in their
|
||||
// latest message, appended after the normal status message
|
||||
const lastUserMessage =
|
||||
[...body.messages]
|
||||
.reverse()
|
||||
.find((m: { role?: string }) => m?.role === "user") ??
|
||||
body.messages[body.messages.length - 1];
|
||||
const echoText = parseEchoText(lastUserMessage?.content);
|
||||
const echoText = parseEchoText(selectMessageSlots(body.messages).lastUser);
|
||||
if (echoText) {
|
||||
responseMessage += `\n\n${echoText}`;
|
||||
}
|
||||
@@ -568,11 +565,13 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const errorMessage = userFacingMessage(error);
|
||||
|
||||
// Check if this was a streaming request
|
||||
// The body already said whether the client wanted a stream; the Accept
|
||||
// header is only a fallback for when the body could not be read at all.
|
||||
const acceptHeader = request.headers.get("accept");
|
||||
const isStreamingRequest = acceptHeader?.includes("text/event-stream");
|
||||
const streaming =
|
||||
isStreamingRequest || (acceptHeader?.includes("text/event-stream") ?? false);
|
||||
|
||||
if (isStreamingRequest) {
|
||||
if (streaming) {
|
||||
return createSSEErrorResponse(errorMessage);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user