fix agent video runtime dependencies
This commit is contained in:
parent
6f0e49bfb9
commit
37db7e2d3a
41
lib/ai/runtime/agent-thread-state.ts
Normal file
41
lib/ai/runtime/agent-thread-state.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
normalizeAgentWorkflowState,
|
||||||
|
type AgentWorkflowState,
|
||||||
|
} from "@/lib/ai/runtime/agent-workflow-state";
|
||||||
|
|
||||||
|
export interface AgentThreadState {
|
||||||
|
workflowState: AgentWorkflowState | null;
|
||||||
|
memoryHints: string[];
|
||||||
|
contextSummary?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText(value: unknown, max = 300): string | undefined {
|
||||||
|
const normalized = String(value || "").trim();
|
||||||
|
if (!normalized) return undefined;
|
||||||
|
return normalized.slice(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMemoryHints(value: unknown): string[] {
|
||||||
|
if (!Array.isArray(value)) return [];
|
||||||
|
return value
|
||||||
|
.map((item) => normalizeText(item, 120))
|
||||||
|
.filter((item): item is string => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeAgentThreadState(
|
||||||
|
value?: Partial<AgentThreadState> | null,
|
||||||
|
): AgentThreadState | null {
|
||||||
|
if (!value || typeof value !== "object") return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
workflowState: normalizeAgentWorkflowState(value.workflowState),
|
||||||
|
memoryHints: normalizeMemoryHints(value.memoryHints),
|
||||||
|
contextSummary: normalizeText(value.contextSummary, 400),
|
||||||
|
metadata:
|
||||||
|
value.metadata && typeof value.metadata === "object" && !Array.isArray(value.metadata)
|
||||||
|
? (value.metadata as Record<string, unknown>)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
114
lib/ai/runtime/agent-workflow-state.ts
Normal file
114
lib/ai/runtime/agent-workflow-state.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
export type AgentWorkflowType =
|
||||||
|
| "storyboard_video"
|
||||||
|
| "high_cost_video"
|
||||||
|
| "image_batch"
|
||||||
|
| "custom";
|
||||||
|
|
||||||
|
export type AgentWorkflowStatus =
|
||||||
|
| "draft"
|
||||||
|
| "awaiting_user_confirmation"
|
||||||
|
| "confirmed"
|
||||||
|
| "rejected"
|
||||||
|
| "running"
|
||||||
|
| "completed"
|
||||||
|
| "failed"
|
||||||
|
| "cancelled";
|
||||||
|
|
||||||
|
export interface AgentWorkflowPendingItem {
|
||||||
|
id?: string;
|
||||||
|
index?: number;
|
||||||
|
title?: string;
|
||||||
|
summary?: string;
|
||||||
|
status?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AgentWorkflowState {
|
||||||
|
workflowId: string;
|
||||||
|
type: AgentWorkflowType;
|
||||||
|
status: AgentWorkflowStatus;
|
||||||
|
title?: string;
|
||||||
|
summary?: string;
|
||||||
|
confirmPhrases: string[];
|
||||||
|
revisionHints: string[];
|
||||||
|
pendingItems: AgentWorkflowPendingItem[];
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText(value: unknown, max = 200): string | undefined {
|
||||||
|
const normalized = String(value || "").trim();
|
||||||
|
if (!normalized) return undefined;
|
||||||
|
return normalized.slice(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeList(value: unknown, fallback: string[]): string[] {
|
||||||
|
const raw = Array.isArray(value) ? value : fallback;
|
||||||
|
const normalized = raw
|
||||||
|
.map((item) => String(item || "").trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
return normalized.length > 0 ? Array.from(new Set(normalized)) : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePendingItems(value: unknown): AgentWorkflowPendingItem[] {
|
||||||
|
if (!Array.isArray(value)) return [];
|
||||||
|
return value
|
||||||
|
.map((item) => {
|
||||||
|
if (!item || typeof item !== "object") return null;
|
||||||
|
const record = item as Record<string, unknown>;
|
||||||
|
const normalized: AgentWorkflowPendingItem = {
|
||||||
|
id: normalizeText(record.id, 80),
|
||||||
|
title: normalizeText(record.title, 80),
|
||||||
|
summary: normalizeText(record.summary, 160),
|
||||||
|
status: normalizeText(record.status, 32),
|
||||||
|
};
|
||||||
|
const rawIndex = Number(record.index);
|
||||||
|
if (Number.isFinite(rawIndex) && rawIndex > 0) {
|
||||||
|
normalized.index = Math.round(rawIndex);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
})
|
||||||
|
.filter((item): item is AgentWorkflowPendingItem => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeAgentWorkflowState(
|
||||||
|
value?: Partial<AgentWorkflowState> | null,
|
||||||
|
): AgentWorkflowState | null {
|
||||||
|
if (!value || typeof value !== "object") return null;
|
||||||
|
|
||||||
|
const workflowId = normalizeText(value.workflowId, 120);
|
||||||
|
const type = normalizeText(value.type, 32) as AgentWorkflowType | undefined;
|
||||||
|
const status = normalizeText(value.status, 40) as AgentWorkflowStatus | undefined;
|
||||||
|
|
||||||
|
if (!workflowId || !type || !status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
workflowId,
|
||||||
|
type,
|
||||||
|
status,
|
||||||
|
title: normalizeText(value.title, 120),
|
||||||
|
summary: normalizeText(value.summary, 240),
|
||||||
|
confirmPhrases: normalizeList(value.confirmPhrases, [
|
||||||
|
"确认开始生成视频",
|
||||||
|
"开始生成",
|
||||||
|
"按这个分镜生成",
|
||||||
|
]),
|
||||||
|
revisionHints: normalizeList(value.revisionHints, [
|
||||||
|
"修改第3镜头",
|
||||||
|
"调整第5镜头动作",
|
||||||
|
"重生第2张高清图",
|
||||||
|
]),
|
||||||
|
pendingItems: normalizePendingItems(value.pendingItems),
|
||||||
|
metadata:
|
||||||
|
value.metadata && typeof value.metadata === "object" && !Array.isArray(value.metadata)
|
||||||
|
? (value.metadata as Record<string, unknown>)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAwaitingUserConfirmationWorkflow(
|
||||||
|
workflowState?: AgentWorkflowState | null,
|
||||||
|
): boolean {
|
||||||
|
return workflowState?.status === "awaiting_user_confirmation";
|
||||||
|
}
|
||||||
|
|
||||||
195
lib/ai/runtime/middlewares/human-confirmation-gate.ts
Normal file
195
lib/ai/runtime/middlewares/human-confirmation-gate.ts
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import {
|
||||||
|
isAwaitingUserConfirmationWorkflow,
|
||||||
|
type AgentWorkflowState,
|
||||||
|
type AgentWorkflowStatus,
|
||||||
|
type AgentWorkflowType,
|
||||||
|
} from "@/lib/ai/runtime/agent-workflow-state";
|
||||||
|
import type { AgentThreadState } from "@/lib/ai/runtime/agent-thread-state";
|
||||||
|
|
||||||
|
export type HumanConfirmationIntent =
|
||||||
|
| "none"
|
||||||
|
| "confirm"
|
||||||
|
| "revise"
|
||||||
|
| "cancel"
|
||||||
|
| "unclear";
|
||||||
|
|
||||||
|
export type HumanConfirmationGateAction =
|
||||||
|
| "pass"
|
||||||
|
| "awaiting_confirmation"
|
||||||
|
| "confirmed"
|
||||||
|
| "revision_requested"
|
||||||
|
| "cancel_requested";
|
||||||
|
|
||||||
|
export interface HumanConfirmationGateDecision {
|
||||||
|
action: HumanConfirmationGateAction;
|
||||||
|
confirmationIntent: HumanConfirmationIntent;
|
||||||
|
workflowId?: string;
|
||||||
|
workflowType?: AgentWorkflowType;
|
||||||
|
nextWorkflowStatus?: AgentWorkflowStatus;
|
||||||
|
allowWorkflowExecution: boolean;
|
||||||
|
shouldBypassPlanner: boolean;
|
||||||
|
friendlyMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIRM_PATTERNS = [
|
||||||
|
/确认开始生成视频/,
|
||||||
|
/确认开始生成/,
|
||||||
|
/按这个分镜生成/,
|
||||||
|
/开始生成视频/,
|
||||||
|
/开始生成$/,
|
||||||
|
/可以开始了/,
|
||||||
|
/可以执行了/,
|
||||||
|
/就按这个/,
|
||||||
|
/没问题开始/,
|
||||||
|
/确认无误/,
|
||||||
|
];
|
||||||
|
|
||||||
|
const REVISE_PATTERNS = [
|
||||||
|
/修改第\d+镜头/,
|
||||||
|
/第\d+镜头/,
|
||||||
|
/调整第\d+镜头/,
|
||||||
|
/重生第\d+/,
|
||||||
|
/重做第\d+/,
|
||||||
|
/改一下/,
|
||||||
|
/修改/,
|
||||||
|
/调整/,
|
||||||
|
/不要这样/,
|
||||||
|
/不对/,
|
||||||
|
/不行/,
|
||||||
|
/换成/,
|
||||||
|
];
|
||||||
|
|
||||||
|
const CANCEL_PATTERNS = [
|
||||||
|
/取消/,
|
||||||
|
/先不要/,
|
||||||
|
/暂停/,
|
||||||
|
/停止/,
|
||||||
|
/先别生成/,
|
||||||
|
/不用生成了/,
|
||||||
|
];
|
||||||
|
|
||||||
|
function normalizeCompactText(text: string) {
|
||||||
|
return String(text || "").toLowerCase().replace(/\s+/g, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function detectHumanConfirmationIntent(text: string): HumanConfirmationIntent {
|
||||||
|
const normalized = normalizeCompactText(text);
|
||||||
|
if (!normalized) return "unclear";
|
||||||
|
|
||||||
|
if (CANCEL_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
||||||
|
return "cancel";
|
||||||
|
}
|
||||||
|
if (REVISE_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
||||||
|
return "revise";
|
||||||
|
}
|
||||||
|
if (CONFIRM_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
||||||
|
return "confirm";
|
||||||
|
}
|
||||||
|
return "unclear";
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAwaitingConfirmationMessage(workflowState: AgentWorkflowState): string {
|
||||||
|
const title = workflowState.title || "当前工作流";
|
||||||
|
const summary = workflowState.summary
|
||||||
|
? `\n当前待确认内容:${workflowState.summary}`
|
||||||
|
: "";
|
||||||
|
const pendingItemsPreview = workflowState.pendingItems
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((item) => {
|
||||||
|
const indexText = item.index ? `镜头${item.index}` : item.title || "待确认项";
|
||||||
|
const summaryText = item.summary ? `:${item.summary}` : "";
|
||||||
|
return `- ${indexText}${summaryText}`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
const previewBlock = pendingItemsPreview ? `\n${pendingItemsPreview}` : "";
|
||||||
|
const confirmExample = workflowState.confirmPhrases[0] || "确认开始生成视频";
|
||||||
|
const reviseExample = workflowState.revisionHints[0] || "修改第3镜头";
|
||||||
|
|
||||||
|
return [
|
||||||
|
`当前有一个待确认流程:${title}。`,
|
||||||
|
summary ? summary.trim() : "",
|
||||||
|
previewBlock.trim(),
|
||||||
|
`如果确认无误,请回复“${confirmExample}”。`,
|
||||||
|
`如果需要修改,请直接回复类似“${reviseExample}”。`,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildConfirmMessage(workflowState: AgentWorkflowState): string {
|
||||||
|
const title = workflowState.title || "当前流程";
|
||||||
|
return `已收到确认:${title} 可以进入下一步执行。我会按已确认内容继续处理。`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRevisionMessage(workflowState: AgentWorkflowState): string {
|
||||||
|
const title = workflowState.title || "当前流程";
|
||||||
|
return `收到修改意见。我会先更新 ${title} 的草案内容,并在重新确认前暂停执行。`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCancelMessage(workflowState: AgentWorkflowState): string {
|
||||||
|
const title = workflowState.title || "当前流程";
|
||||||
|
return `已收到取消指令。${title} 会继续停留在人工确认前状态,不会继续自动执行。`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runHumanConfirmationGate(params: {
|
||||||
|
text: string;
|
||||||
|
threadState?: AgentThreadState | null;
|
||||||
|
}): HumanConfirmationGateDecision {
|
||||||
|
const workflowState = params.threadState?.workflowState;
|
||||||
|
if (!workflowState || !isAwaitingUserConfirmationWorkflow(workflowState)) {
|
||||||
|
return {
|
||||||
|
action: "pass",
|
||||||
|
confirmationIntent: "none",
|
||||||
|
allowWorkflowExecution: true,
|
||||||
|
shouldBypassPlanner: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmationIntent = detectHumanConfirmationIntent(params.text);
|
||||||
|
const baseDecision = {
|
||||||
|
workflowId: workflowState.workflowId,
|
||||||
|
workflowType: workflowState.type,
|
||||||
|
shouldBypassPlanner: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (confirmationIntent) {
|
||||||
|
case "confirm":
|
||||||
|
return {
|
||||||
|
...baseDecision,
|
||||||
|
action: "confirmed",
|
||||||
|
confirmationIntent,
|
||||||
|
nextWorkflowStatus: "confirmed",
|
||||||
|
allowWorkflowExecution: true,
|
||||||
|
friendlyMessage: buildConfirmMessage(workflowState),
|
||||||
|
};
|
||||||
|
case "revise":
|
||||||
|
return {
|
||||||
|
...baseDecision,
|
||||||
|
action: "revision_requested",
|
||||||
|
confirmationIntent,
|
||||||
|
nextWorkflowStatus: "draft",
|
||||||
|
allowWorkflowExecution: false,
|
||||||
|
friendlyMessage: buildRevisionMessage(workflowState),
|
||||||
|
};
|
||||||
|
case "cancel":
|
||||||
|
return {
|
||||||
|
...baseDecision,
|
||||||
|
action: "cancel_requested",
|
||||||
|
confirmationIntent,
|
||||||
|
nextWorkflowStatus: "cancelled",
|
||||||
|
allowWorkflowExecution: false,
|
||||||
|
friendlyMessage: buildCancelMessage(workflowState),
|
||||||
|
};
|
||||||
|
case "unclear":
|
||||||
|
case "none":
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
...baseDecision,
|
||||||
|
action: "awaiting_confirmation",
|
||||||
|
confirmationIntent: "unclear",
|
||||||
|
nextWorkflowStatus: workflowState.status,
|
||||||
|
allowWorkflowExecution: false,
|
||||||
|
friendlyMessage: buildAwaitingConfirmationMessage(workflowState),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user