mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-10-18 01:20:29 +00:00
Read the request by role, not by position
Some users' JanitorAI sends a "." user turn between the card and the greeting; others' does not. Sucker read fixed indices, so on the shorter shape every slot was off by one: first_mes was set to the name line, and the name slot did not exist at all. That TypeError was caught by the handler and reported as the "you dingus, read the directions" message, which blamed the user for a shape they never chose. src/lib/messages.ts now resolves the slots by role instead — the card is the system turn, the greeting is the first assistant turn, the user's latest input is the last user turn, and sucker's own previous reply is the last assistant turn. All four hold in both shapes and in a continued conversation, so the conversation token is found in either as well. Extraction now throws a description of what is missing rather than a TypeError, and tests/messages.test.ts pins both shapes. The tests need no framework: `npm test` runs node's built-in runner. Existing cards are unaffected: the update path already discards the extracted name and keeps the original. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
This commit is contained in:
+20
-10
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { selectMessageSlots, type Message } from "@/lib/messages";
|
||||
|
||||
interface CardVersion {
|
||||
version: number;
|
||||
@@ -64,10 +65,6 @@ function cleanupExpiredCards() {
|
||||
);
|
||||
}
|
||||
|
||||
interface Message {
|
||||
content: string;
|
||||
}
|
||||
|
||||
// Extracted shape used during POST handling
|
||||
interface ExtractedCard {
|
||||
trackingName: string;
|
||||
@@ -118,9 +115,18 @@ function removePersonaTags(content: string): string {
|
||||
}
|
||||
|
||||
function extractCardData(messages: Message[]): ExtractedCard {
|
||||
const first_mes = messages[2].content.replace(/{user}/g, "{{user}}");
|
||||
const { system, greeting, lastUser } = selectMessageSlots(messages);
|
||||
|
||||
const nameContent = messages[3].content;
|
||||
if (!system?.content) {
|
||||
throw new Error("No system message: this request carries no character card.");
|
||||
}
|
||||
if (!greeting?.content) {
|
||||
throw new Error("No assistant message: this chat has no greeting to read.");
|
||||
}
|
||||
|
||||
const first_mes = greeting.content.replace(/{user}/g, "{{user}}");
|
||||
|
||||
const nameContent = lastUser?.content ?? "";
|
||||
// If the name slot is actually a token, ignore it for naming purposes
|
||||
const tokenInNameSlot = parseConversationToken(nameContent);
|
||||
const lastColonIndex = nameContent.lastIndexOf(": ");
|
||||
@@ -130,7 +136,7 @@ function extractCardData(messages: Message[]): ExtractedCard {
|
||||
? nameContent.substring(lastColonIndex + 2).trim()
|
||||
: "";
|
||||
|
||||
let content = messages[0].content.replace(/{user}/g, "{{user}}");
|
||||
let content = system.content.replace(/{user}/g, "{{user}}");
|
||||
const inferredName = extractPersonaName(content);
|
||||
content = removePersonaTags(content);
|
||||
|
||||
@@ -340,9 +346,13 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Parse potential token from messages[3] (user) or messages[4] (assistant prior reply)
|
||||
const tokenCandidateUser: string | undefined = body.messages?.[3]?.content;
|
||||
const tokenCandidateAssistant: string | undefined = body.messages?.[4]?.content;
|
||||
// The token is either in what the user just typed, or in sucker's own
|
||||
// previous reply. Both are found by role, so a missing "." turn or a
|
||||
// longer conversation cannot shift them out from under us.
|
||||
const tokenSlots = selectMessageSlots(body.messages);
|
||||
const tokenCandidateUser: string | undefined = tokenSlots.lastUser?.content;
|
||||
const tokenCandidateAssistant: string | undefined =
|
||||
tokenSlots.lastAssistant?.content;
|
||||
const providedConversationId =
|
||||
parseConversationToken(tokenCandidateUser || undefined) ||
|
||||
parseConversationToken(tokenCandidateAssistant || undefined);
|
||||
|
||||
Reference in New Issue
Block a user