38 lines
4.2 KiB
JavaScript
38 lines
4.2 KiB
JavaScript
import { desktopAssert, desktopId, desktopProjectBody } from './contract.mjs'
|
|
|
|
export const taskUuid = value => typeof value === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value)
|
|
export const taskDigest = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)
|
|
export const taskBusy = ['queued', 'running', 'received', 'unknown']
|
|
const text = (value, max) => typeof value === 'string' && value.length <= max
|
|
const taskId = value => value === null || typeof value === 'string' && /^[A-Za-z0-9_-]{1,160}$/.test(value)
|
|
export function validateCreationTask(value, projectId) {
|
|
desktopAssert(value && taskUuid(value.id) && ['facts','points','plan','image'].includes(value.stage), '任务编号或类型无效')
|
|
const document = desktopProjectBody({ document: structuredClone(value.document), status: 'draft', revision: 0 }).document
|
|
desktopAssert(document.version === 2 && document.id === desktopId(projectId), '任务必须属于当前创作项目')
|
|
desktopAssert(value.input && taskDigest(value.input.hash) && text(value.input.prompt, 40000) && taskDigest(value.keyHash), '任务检查点无效')
|
|
desktopAssert(value.model && value.model.baseUrl === 'https://api.vip98k.com/v1' && taskDigest(value.model.fingerprint) && text(value.model.id, 200) && text(value.model.key, 200) && value.model.protocol, '模型执行契约无效')
|
|
// Project/task snapshots must never become a credential transport.
|
|
const encoded = JSON.stringify(value)
|
|
desktopAssert(encoded.length < 650000 && !/"(?:api_?key|access_?token|authorization|password|secret)"\s*:/i.test(encoded), '任务包含敏感字段或体积过大')
|
|
if (value.stage === 'image') desktopAssert(document.plan.status === 'confirmed' && document.plan.screens.some(screen => screen.id === value.screenId) && taskDigest(value.input.screenHash) && taskDigest(value.input.planHash), '先确认当前页面方案')
|
|
return { id: value.id, stage: value.stage, model: value.model, document, input: value.input, keyHash: value.keyHash,
|
|
screenId: value.screenId || null, batchId: value.batchId || null,
|
|
status: 'queued', dispatched: false, requestId: null, remoteTaskId: null, text: null, artifact: null,
|
|
createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), message: '已登记客户端任务', applied: false }
|
|
}
|
|
export function validateTaskPatch(task, patch, recovery) {
|
|
desktopAssert(patch && typeof patch === 'object' && Object.keys(patch).every(key => ['status','dispatched','requestId','remoteTaskId','text','artifact','summary','result','applied','message'].includes(key)), '不允许改写原任务输入')
|
|
const result = { ...task, ...patch }
|
|
desktopAssert(['queued','running','received','succeeded','failed','unknown','cancelled'].includes(result.status) && text(result.message, 500), '任务状态无效')
|
|
desktopAssert(!task.dispatched || result.dispatched === true, '已发送的请求不能退回未发送')
|
|
desktopAssert(!recovery || result.dispatched === task.dispatched, '恢复任务只允许读取原结果')
|
|
desktopAssert(taskId(result.requestId) && taskId(result.remoteTaskId) && (!task.requestId || task.requestId === result.requestId) && (!task.remoteTaskId || task.remoteTaskId === result.remoteTaskId), '不能替换原请求编号')
|
|
desktopAssert(!result.dispatched || result.status !== 'queued' && result.status !== 'cancelled', '已发送请求不能标记为取消执行')
|
|
desktopAssert(!['succeeded','failed','cancelled'].includes(task.status)||recovery, '任务已结束')
|
|
if (result.text !== null && result.text !== undefined) desktopAssert(text(result.text,120000), '原响应过大')
|
|
if (result.artifact) desktopAssert(/^local:[a-zA-Z0-9-]{1,100}$/.test(result.artifact.source || '') && taskDigest(result.artifact.sha256) && Number.isInteger(result.artifact.width) && Number.isInteger(result.artifact.height) && result.artifact.width > 0 && result.artifact.height > 0 && result.artifact.width * result.artifact.height <= 24000000, '图片检查点无效')
|
|
const encoded = JSON.stringify(result)
|
|
desktopAssert(encoded.length < 850000 && !/"(?:api_?key|access_?token|authorization|password|secret)"\s*:/i.test(encoded), '任务结果包含敏感字段或体积过大')
|
|
return result
|
|
}
|