feat(memory): implement stability stage
Some checks failed
CI / validate (push) Has been cancelled
CI / validate (pull_request) Has been cancelled

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-09-02 15:04:18 +05:00
parent 4cd0b7b390
commit 031d094867
24 changed files with 1661 additions and 79 deletions

View File

@@ -0,0 +1,40 @@
import { z } from 'zod'
import {
requireCampaignAccess,
requireStageTwoSafeText,
requireStageTwoUser,
stageTwoApiError,
stageTwoRpc,
stageTwoUuid,
} from '~/server/utils/stage-two-supabase'
const BodySchema = z.object({
hp: z.number().int().min(0).optional(),
inventory: z.array(z.string().trim().min(1).max(120)).max(50).optional(),
statuses: z.array(z.string().trim().min(1).max(80)).max(12).optional(),
reason: z.string().trim().min(3).max(500),
}).strict().refine(body => body.hp !== undefined || body.inventory !== undefined || body.statuses !== undefined, {
message: 'At least one state field is required.',
})
export default defineEventHandler(async (event) => {
try {
const user = await requireStageTwoUser(event)
const campaignId = stageTwoUuid(getRouterParam(event, 'id'), 'campaign id')
const characterId = stageTwoUuid(getRouterParam(event, 'characterId'), 'character id')
await requireCampaignAccess(campaignId, user.id, true)
const body = BodySchema.parse(await readBody(event))
requireStageTwoSafeText([body.reason, ...(body.inventory ?? []), ...(body.statuses ?? [])].join('\n'))
const { reason, ...patch } = body
const state = await stageTwoRpc<Record<string, unknown>>('stage_six_adjust_character_state', {
p_campaign_id: campaignId,
p_owner_id: user.id,
p_character_id: characterId,
p_patch: patch,
p_reason: reason,
})
return { characterId, state }
} catch (error) {
stageTwoApiError(error)
}
})

View File

@@ -5,6 +5,8 @@ import {
requireCampaignAccess,
requireStageTwoSafeText,
requireStageTwoUser,
stageSixConsumeAiQuota,
stageSixRecordAiUsage,
stageTwoApiError,
stageTwoDatabase,
stageTwoUuid,
@@ -48,8 +50,10 @@ export default defineEventHandler(async (event) => {
jsonSchema: CharacterDraftSchema.toJSONSchema(),
})
await stageSixConsumeAiQuota(user.id, campaignId, 'character-draft')
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 30_000)
const startedAt = Date.now()
let response: Response
try {
response = await fetch(completion.endpoint, {
@@ -62,10 +66,14 @@ export default defineEventHandler(async (event) => {
clearTimeout(timeout)
}
if (!response.ok) throw createError({ statusCode: 502, statusMessage: 'The character coauthor is temporarily unavailable.' })
const payload = await response.json()
await stageSixRecordAiUsage({
userId: user.id, campaignId, requestKind: 'character-draft', completion, payload, latencyMs: Date.now() - startedAt,
}).catch(error => console.error('[web] could not record character draft usage:', error))
let character
try {
character = CharacterDraftSchema.parse(parseJsonCompletion(await response.json()))
character = CharacterDraftSchema.parse(parseJsonCompletion(payload))
} catch {
throw createError({ statusCode: 502, statusMessage: 'The coauthor returned an invalid character draft.' })
}