feat: require validation receipts for h5 code runs
This commit is contained in:
+43
-2
@@ -58,7 +58,7 @@ import type {
|
||||
} from '../types';
|
||||
import { CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES } from '../utils/imageUpload';
|
||||
import { normalizeConversationMessages, normalizeUserMessageForApi } from '../utils/message';
|
||||
import type { AgentRunCreateOptions } from '../utils/agentRunMode';
|
||||
import type { AgentRunCreateOptions, AgentRunValidation } from '../utils/agentRunMode';
|
||||
|
||||
const API = '/api';
|
||||
const DEFAULT_API_TIMEOUT_MS = 20_000;
|
||||
@@ -79,6 +79,46 @@ export type AgentRun = {
|
||||
completedAt: number | null;
|
||||
};
|
||||
|
||||
function appendAgentRunValidationInstruction(message: Message, instruction?: string | null): Message {
|
||||
const normalizedInstruction = String(instruction ?? '').trim();
|
||||
if (!normalizedInstruction) return message;
|
||||
return {
|
||||
...message,
|
||||
content: [
|
||||
...message.content,
|
||||
{ type: 'text', text: normalizedInstruction },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function withAgentRunValidationMetadata(
|
||||
message: Message,
|
||||
validation?: AgentRunValidation | null,
|
||||
): Message {
|
||||
if (!validation) return message;
|
||||
const metadata = message.metadata as Message['metadata'] & {
|
||||
memindRun?: Record<string, unknown>;
|
||||
};
|
||||
return {
|
||||
...message,
|
||||
metadata: {
|
||||
...message.metadata,
|
||||
memindRun: {
|
||||
...(metadata.memindRun && typeof metadata.memindRun === 'object' ? metadata.memindRun : {}),
|
||||
validation,
|
||||
},
|
||||
} as Message['metadata'],
|
||||
};
|
||||
}
|
||||
|
||||
function prepareAgentRunUserMessage(message: Message, options: AgentRunCreateOptions): Message {
|
||||
const normalized = normalizeUserMessageForApi(message);
|
||||
return appendAgentRunValidationInstruction(
|
||||
withAgentRunValidationMetadata(normalized, options.validation),
|
||||
options.toolMode === 'code' ? options.validationInstruction : null,
|
||||
);
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
readonly status: number;
|
||||
readonly code?: string;
|
||||
@@ -2250,6 +2290,7 @@ export async function createAgentRun(
|
||||
userMessage: Message,
|
||||
options: AgentRunCreateOptions = {},
|
||||
): Promise<AgentRun> {
|
||||
const userMessagePayload = prepareAgentRunUserMessage(userMessage, options);
|
||||
const result = await apiFetch<{ run: AgentRun }>(
|
||||
AGENT_RUNS_PATH,
|
||||
{
|
||||
@@ -2257,7 +2298,7 @@ export async function createAgentRun(
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
request_id: requestId,
|
||||
user_message: normalizeUserMessageForApi(userMessage),
|
||||
user_message: userMessagePayload,
|
||||
...(options.toolMode ? { tool_mode: options.toolMode } : {}),
|
||||
...(options.taskType ? { task_type: options.taskType } : {}),
|
||||
}),
|
||||
|
||||
@@ -348,6 +348,7 @@ export function usePageEditSubChat({
|
||||
taskType: 'page_edit_code_task',
|
||||
forceCode: true,
|
||||
userId: user?.id ?? null,
|
||||
requestId,
|
||||
}),
|
||||
);
|
||||
const finishedRun =
|
||||
|
||||
@@ -1176,6 +1176,7 @@ export function useTKMindChat(
|
||||
resolveAgentRunOptions(trimmed, {
|
||||
taskType: 'h5_chat_code_task',
|
||||
userId: userRef.current?.id ?? null,
|
||||
requestId,
|
||||
}),
|
||||
);
|
||||
const finishedRun =
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
export type AgentRunCreateOptions = {
|
||||
toolMode?: 'chat' | 'code';
|
||||
taskType?: string | null;
|
||||
validation?: AgentRunValidation | null;
|
||||
validationInstruction?: string | null;
|
||||
};
|
||||
|
||||
export type AgentRunValidationFile = {
|
||||
path: string;
|
||||
contains?: string;
|
||||
};
|
||||
|
||||
export type AgentRunValidation = {
|
||||
expectedFile?: AgentRunValidationFile | string;
|
||||
expectedFiles?: Array<AgentRunValidationFile | string>;
|
||||
};
|
||||
|
||||
function envFlag(value: unknown): boolean {
|
||||
@@ -38,6 +50,36 @@ const CODE_TASK_PATTERNS = [
|
||||
/(代码|仓库|项目|文件|组件|接口).{0,12}(修改|修复|重构|调试|实现|新增|编写|更新)/,
|
||||
];
|
||||
|
||||
function sanitizeRequestIdForPath(requestId: string): string {
|
||||
const normalized = String(requestId ?? '').trim().replace(/[^a-zA-Z0-9._-]/g, '-');
|
||||
return normalized || 'unknown-request';
|
||||
}
|
||||
|
||||
export function buildAgentRunValidationReceipt(requestId: string): {
|
||||
validation: AgentRunValidation;
|
||||
instruction: string;
|
||||
} {
|
||||
const normalizedRequestId = String(requestId ?? '').trim();
|
||||
const safeRequestId = sanitizeRequestIdForPath(normalizedRequestId);
|
||||
const receiptPath = `.memind/agent-runs/${safeRequestId}.json`;
|
||||
const marker = normalizedRequestId || safeRequestId;
|
||||
return {
|
||||
validation: {
|
||||
expectedFile: {
|
||||
path: receiptPath,
|
||||
contains: marker,
|
||||
},
|
||||
},
|
||||
instruction: [
|
||||
'',
|
||||
'[Memind code-run validation]',
|
||||
`Before finishing, create or update ${receiptPath}.`,
|
||||
`The file must be valid JSON and include this requestId value: ${marker}.`,
|
||||
'Do not skip this receipt even if the user task itself is complete.',
|
||||
].join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveAgentRunOptions(
|
||||
text: string,
|
||||
{
|
||||
@@ -45,19 +87,24 @@ export function resolveAgentRunOptions(
|
||||
forceCode = false,
|
||||
allowAutodetect = agentCodeRunsAutodetectEnabled,
|
||||
userId = null,
|
||||
requestId = null,
|
||||
}: {
|
||||
taskType?: string;
|
||||
forceCode?: boolean;
|
||||
allowAutodetect?: boolean;
|
||||
userId?: string | null;
|
||||
requestId?: string | null;
|
||||
} = {},
|
||||
): AgentRunCreateOptions {
|
||||
if (!agentCodeRunsEnabledForUser(userId)) return {};
|
||||
const normalizedText = String(text ?? '').trim();
|
||||
const shouldUseCode = forceCode || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
|
||||
if (!shouldUseCode) return {};
|
||||
const receipt = buildAgentRunValidationReceipt(requestId ?? crypto.randomUUID());
|
||||
return {
|
||||
toolMode: 'code',
|
||||
taskType,
|
||||
validation: receipt.validation,
|
||||
validationInstruction: receipt.instruction,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user