完成MiniMax-M2.7思考模式适配

This commit is contained in:
ACT丶流星雨 2026-03-30 15:57:39 +08:00
parent e306a14c11
commit 995dc5ae97
13 changed files with 956 additions and 139 deletions

View File

@ -1,3 +1,9 @@
---
name: director_planning
description: 导演规划技法,定义古风甜宠写实超现实主义在主题立意、视觉基调、叙事节奏、场景意图与声音设计上的全局规划方法。
metaData: director_skills
---
# 导演规划 · 古风甜宠写实超现实主义 · 风格技法参考
---

View File

@ -1,3 +1,9 @@
---
name: director_storyboard_table
description: 分镜表设计技法,规范古风甜宠写实超现实主义在景别、运镜、时长、动作、光影与转场上的镜头语言表达。
metaData: director_skills
---
# 分镜表设计 · 古风甜宠写实超现实主义 · 风格技法参考
---

View File

@ -70,6 +70,7 @@
"sqlite3": "^6.0.1",
"sucrase": "^3.35.1",
"uuid": "^13.0.0",
"vercel-minimax-ai-provider": "^0.0.2",
"vm2": "^3.10.5",
"zhipu-ai-provider": "^0.2.2",
"zod": "^4.3.5"

View File

@ -3,7 +3,7 @@ import { tool } from "ai";
import { z } from "zod";
import u from "@/utils";
import Memory from "@/utils/agent/memory";
import { useSkill } from "@/utils/agent/skillsTools";
import { buildSkillPrompt, createSkillTools, parseFrontmatter, scanSkills, useSkill } from "@/utils/agent/skillsTools";
import useTools from "@/agents/productionAgent/tools";
import ResTool from "@/socket/resTool";
import * as fs from "fs";
@ -77,12 +77,14 @@ function createSubAgent(parentCtx: AgentContext) {
name,
memoryKey,
tools: extraTools,
messages,
}: {
prompt: string;
system: string;
name: string;
memoryKey: string;
tools?: Record<string, any>;
messages?: { role: "user" | "assistant" | "system"; content: string }[];
}) {
parentCtx.msg.complete();
const subMsg = resTool.newMessage("assistant", name);
@ -91,7 +93,7 @@ function createSubAgent(parentCtx: AgentContext) {
const { textStream } = await u.Ai.Text("scriptAgent").stream({
system,
messages: [{ role: "user", content: prompt }],
messages: messages ?? [{ role: "user", content: prompt }],
abortSignal,
tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) },
});
@ -135,11 +137,20 @@ function createSubAgent(parentCtx: AgentContext) {
"```",
].join("\n");
const projectInfo = await u.db("o_project").where("id", resTool.data.projectId).first();
if (!projectInfo) throw new Error(`项目不存在ID: ${resTool.data.projectId}`);
const artSkills = await createArtSkills(projectInfo?.artStyle!);
return runAgent({
prompt,
system: systemPrompt + addPrompt,
name: "执行导演",
memoryKey: "assistant:execution",
messages: [
{ role: "assistant", content: artSkills.prompt },
{ role: "user", content: prompt },
],
tools: { ...artSkills.tools },
});
},
});
@ -162,89 +173,18 @@ function createSubAgent(parentCtx: AgentContext) {
return { run_sub_agent_execution, run_sub_agent_supervision };
}
// //====================== 执行层 ======================
// export async function executionAI(ctx: AgentContext) {
// const { text, abortSignal } = ctx;
// const skill = await useSkill({
// mainSkill: "production_agent_execution",
// workspace: ["production_agent_skills/execution"],
// attachedSkills: ["production_agent_skills/execution/driector_art_skills/chinese_sweet_romance/driector_skills"], //todo后续可以改为动态加载
// });
// const subMsg = ctx.resTool.newMessage("assistant", "执行导演");
// const { textStream } = await u.Ai.Text("productionAgent").stream({
// system: skill.prompt,
// messages: [{ role: "user", content: text }],
// abortSignal,
// tools: {
// ...skill.tools,
// ...useTools({ resTool: ctx.resTool, msg: subMsg }),
// },
// });
// return { textStream, subMsg };
// }
// export async function supervisionAI(ctx: AgentContext) {
// const { text, abortSignal } = ctx;
// const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] });
// const subMsg = ctx.resTool.newMessage("assistant", "监制");
// const { textStream } = await u.Ai.Text("productionAgent").stream({
// system: skill.prompt,
// messages: [{ role: "user", content: text }],
// abortSignal,
// tools: {
// ...skill.tools,
// ...useTools({
// resTool: ctx.resTool,
// msg: subMsg,
// }),
// },
// });
// return { textStream, subMsg };
// }
// //工具函数
// function runSubAgent(parentCtx: AgentContext) {
// const memory = new Memory("productionAgent", parentCtx.isolationKey);
// return tool({
// description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI",
// inputSchema: z.object({
// agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"),
// prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
// }),
// execute: async ({ agent, prompt }) => {
// const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)];
// // 先完成主Agent当前的消息
// parentCtx.msg.complete();
// // 子Agent用新消息回复
// const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt });
// let text = subMsg.text();
// let fullResponse = "";
// for await (const chunk of subTextStream) {
// text.append(chunk);
// fullResponse += chunk;
// }
// text.complete();
// subMsg.complete();
// if (fullResponse.trim()) {
// await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, {
// name: agent === "executionAI" ? "执行导演" : "监制",
// createTime: new Date(subMsg.datetime).getTime(),
// });
// }
// // 为主Agent后续输出创建新消息
// parentCtx.msg = parentCtx.resTool.newMessage("assistant", "监制");
// return fullResponse;
// },
// });
// }
async function createArtSkills(artName: string) {
const path = u.getPath(["skills", "art_prompts", artName, "driector_skills"]);
const skillList = await scanSkills(path + "/*.md");
const mainSkills: { path: string; name: string; description: string }[] = [];
for (const skillPath of skillList) {
if (!fs.existsSync(skillPath)) throw new Error(`主技能文件不存在: ${skillPath}`);
const content = await fs.promises.readFile(skillPath, "utf-8");
const parsed = parseFrontmatter(content);
mainSkills.push({ path: skillPath, ...parsed });
}
return {
prompt: buildSkillPrompt(mainSkills),
tools: createSkillTools(mainSkills, { mainSkill: mainSkills, secondarySkills: [], tertiarySkills: [] }),
};
}

View File

@ -23,9 +23,6 @@ export default router.post(
if (allChapters.length === 0) {
return res.status(400).send(success("没有对应章节"));
}
if (allChapters.filter((item) => item.eventState === 0).length) {
return res.status(400).send(success("存在未完成事件,请先等待事件完成"));
}
await u.db("o_novel").where("projectId", projectId).whereIn("id", novelIds).update({ eventState: 0, event: null });
novel.emitter.on("item", async (item) => {
await u

663
src/socket/resTool copy.ts Normal file
View File

@ -0,0 +1,663 @@
import u from "@/utils";
import { Socket } from "socket.io";
import type {
ChatMessageStatus,
AIMessageContent,
TextContent,
MarkdownContent,
ImageContent,
ThinkingContent,
SearchContent,
SuggestionContent,
ToolCallContent,
ActivityContent,
ReasoningContent,
} from "./chatMessagesData";
type ContentType = AIMessageContent["type"];
class ResTool {
public socket: Socket;
public data: Record<string, any>;
constructor(socket: Socket, data: Record<string, any> = {}) {
this.socket = socket;
this.data = data;
}
// 创建新消息
newMessage(role: "assistant" | "user" | "system" = "assistant", name?: string) {
const messageId = u.uuid();
const datetime = new Date().toISOString();
this.socket.emit("message", {
id: messageId,
role,
name,
status: "pending" as ChatMessageStatus,
datetime,
content: [],
});
return new MessageBuilder(this.socket, messageId, role, name, datetime);
}
// 发送错误消息
sendError(messageId: string, error: string) {
this.socket.emit("message:update", {
id: messageId,
status: "error" as ChatMessageStatus,
ext: { error },
});
}
// 发送完成状态
sendComplete(messageId: string) {
this.socket.emit("message:update", {
id: messageId,
status: "complete" as ChatMessageStatus,
});
}
}
// 消息构建器
class MessageBuilder {
private socket: Socket;
private messageId: string;
private messageRole: "assistant" | "user" | "system";
private messageName?: string;
private messageDatetime: string;
constructor(socket: Socket, messageId: string, role: "assistant" | "user" | "system", name?: string, datetime?: string) {
this.socket = socket;
this.messageId = messageId;
this.messageRole = role;
this.messageName = name;
this.messageDatetime = datetime ?? new Date().toISOString();
}
get id() {
return this.messageId;
}
get role() {
return this.messageRole;
}
get name() {
return this.messageName;
}
get datetime() {
return this.messageDatetime;
}
// 更新消息状态
updateStatus(status: ChatMessageStatus) {
this.socket.emit("message:update", {
id: this.messageId,
status,
});
return this;
}
// 添加文本内容
text(initialText = "") {
const contentId = u.uuid();
const content: TextContent = {
type: "text",
id: contentId,
data: "",
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
const stream = new AutoThinkingTextStream(this.socket, this.messageId, contentId, this);
if (initialText) {
stream.append(initialText);
}
return stream;
}
// 添加 Markdown 内容
markdown(initialText = "") {
const contentId = u.uuid();
const content: MarkdownContent = {
type: "markdown",
id: contentId,
data: initialText,
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ContentStream<string>(this.socket, this.messageId, contentId, "markdown");
}
// 添加思考内容
thinking(title = "思考中...") {
const contentId = u.uuid();
const content: ThinkingContent = {
type: "thinking",
id: contentId,
data: { title, text: "" },
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ThinkingStream(this.socket, this.messageId, contentId);
}
// 添加搜索内容
search(title = "搜索中...") {
const contentId = u.uuid();
const content: SearchContent = {
type: "search",
id: contentId,
data: { title, references: [] },
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new SearchStream(this.socket, this.messageId, contentId);
}
// 添加图片内容
image(data: ImageContent["data"]) {
const contentId = u.uuid();
const content: ImageContent = {
type: "image",
id: contentId,
data,
status: "complete",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return this;
}
// 添加建议内容
suggestion(suggestions: SuggestionContent["data"]) {
const contentId = u.uuid();
const content: SuggestionContent = {
type: "suggestion",
id: contentId,
data: suggestions,
status: "complete",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return this;
}
// 添加工具调用内容
toolCall(data: ToolCallContent["data"]) {
const contentId = u.uuid();
const content: ToolCallContent = {
type: "toolcall",
id: contentId,
data: { ...data, parentMessageId: this.messageId },
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ToolCallStream(this.socket, this.messageId, contentId, data.toolCallId);
}
// 添加活动内容
activity<T = Record<string, any>>(activityType: string, content: T) {
const contentId = u.uuid();
const activityContent: ActivityContent<T> = {
type: "activity",
id: contentId,
data: {
activityType,
messageId: this.messageId,
content,
},
status: "complete",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content: activityContent,
});
return this;
}
// 添加推理内容
reasoning() {
const contentId = u.uuid();
const content: ReasoningContent = {
type: "reasoning",
id: contentId,
data: [],
status: "pending",
};
this.socket.emit("content:add", {
messageId: this.messageId,
content,
});
return new ReasoningBuilder(this.socket, this.messageId, contentId);
}
// 完成消息
complete() {
this.socket.emit("message:update", {
id: this.messageId,
status: "complete" as ChatMessageStatus,
});
}
// 停止消息
stop() {
this.socket.emit("message:update", {
id: this.messageId,
status: "stop" as ChatMessageStatus,
});
}
// 错误
error(errorMsg?: string) {
this.socket.emit("message:update", {
id: this.messageId,
status: "error" as ChatMessageStatus,
ext: errorMsg ? { error: errorMsg } : undefined,
});
}
}
// 内容流基类
class ContentStream<T> {
protected socket: Socket;
protected messageId: string;
protected contentId: string;
protected contentType: ContentType;
constructor(socket: Socket, messageId: string, contentId: string, contentType: ContentType) {
this.socket = socket;
this.messageId = messageId;
this.contentId = contentId;
this.contentType = contentType;
}
get id() {
return this.contentId;
}
// 流式追加数据
append(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: this.contentType,
data: chunk,
strategy: "append",
status: "streaming",
});
return this;
}
// 合并/替换数据
merge(data: T) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: this.contentType,
data,
strategy: "merge",
status: "streaming",
});
return this;
}
// 完成内容
complete(finalData?: T) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: this.contentType,
data: finalData,
status: "complete",
});
return this;
}
// 错误
error() {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
status: "error",
});
return this;
}
}
// 思考内容流
class ThinkingStream extends ContentStream<ThinkingContent["data"]> {
constructor(socket: Socket, messageId: string, contentId: string) {
super(socket, messageId, contentId, "thinking");
}
// 追加思考文本
appendText(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "thinking",
data: { text: chunk },
strategy: "append",
status: "streaming",
});
return this;
}
// 更新标题
updateTitle(title: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "thinking",
data: { title },
strategy: "merge",
status: "streaming",
});
return this;
}
}
// 文本内容流:自动把 <think>...</think> 转为 thinking 内容
class AutoThinkingTextStream extends ContentStream<string> {
private static readonly OPEN_TAG = "<think>";
private static readonly CLOSE_TAG = "</think>";
private readonly messageBuilder: MessageBuilder;
private pending = "";
private inThinking = false;
private thinkingStream: ThinkingStream | null = null;
constructor(socket: Socket, messageId: string, contentId: string, messageBuilder: MessageBuilder) {
super(socket, messageId, contentId, "text");
this.messageBuilder = messageBuilder;
}
override append(chunk: string) {
if (!chunk) return this;
let rest = this.pending + chunk;
this.pending = "";
while (rest.length > 0) {
if (!this.inThinking) {
const openIndex = rest.indexOf(AutoThinkingTextStream.OPEN_TAG);
if (openIndex < 0) {
const keepLen = AutoThinkingTextStream.OPEN_TAG.length - 1;
const flushLen = Math.max(0, rest.length - keepLen);
if (flushLen > 0) {
this.appendText(rest.slice(0, flushLen));
rest = rest.slice(flushLen);
}
this.pending = rest;
break;
}
this.appendText(rest.slice(0, openIndex));
this.inThinking = true;
this.ensureThinkingStream();
rest = rest.slice(openIndex + AutoThinkingTextStream.OPEN_TAG.length);
continue;
}
const closeIndex = rest.indexOf(AutoThinkingTextStream.CLOSE_TAG);
if (closeIndex < 0) {
const keepLen = AutoThinkingTextStream.CLOSE_TAG.length - 1;
const flushLen = Math.max(0, rest.length - keepLen);
if (flushLen > 0) {
this.appendThinking(rest.slice(0, flushLen));
rest = rest.slice(flushLen);
}
this.pending = rest;
break;
}
this.appendThinking(rest.slice(0, closeIndex));
this.finishThinking();
rest = rest.slice(closeIndex + AutoThinkingTextStream.CLOSE_TAG.length);
}
return this;
}
override complete(finalData?: string) {
if (finalData) {
this.append(finalData);
}
if (this.pending) {
if (this.inThinking) {
this.appendThinking(this.pending);
} else {
this.appendText(this.pending);
}
this.pending = "";
}
this.finishThinking();
super.complete();
return this;
}
override error() {
if (this.thinkingStream) {
this.thinkingStream.error();
this.thinkingStream = null;
}
this.pending = "";
this.inThinking = false;
return super.error();
}
private appendText(text: string) {
if (!text) return;
super.append(text);
}
private appendThinking(text: string) {
if (!text) return;
this.ensureThinkingStream().appendText(text);
}
private ensureThinkingStream() {
if (!this.thinkingStream) {
this.thinkingStream = this.messageBuilder.thinking("思考中...");
}
return this.thinkingStream;
}
private finishThinking() {
if (this.thinkingStream) {
this.thinkingStream.complete();
this.thinkingStream = null;
}
this.inThinking = false;
}
}
// 搜索内容流
class SearchStream extends ContentStream<SearchContent["data"]> {
constructor(socket: Socket, messageId: string, contentId: string) {
super(socket, messageId, contentId, "search");
}
// 添加引用
addReference(ref: Exclude<SearchContent["data"]["references"], undefined>[0]) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "search",
data: { references: [ref] },
strategy: "append",
status: "streaming",
});
return this;
}
// 批量添加引用
addReferences(refs: SearchContent["data"]["references"]) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "search",
data: { references: refs },
strategy: "append",
status: "streaming",
});
return this;
}
// 更新标题
updateTitle(title: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "search",
data: { title },
strategy: "merge",
status: "streaming",
});
return this;
}
}
// 工具调用流
class ToolCallStream extends ContentStream<ToolCallContent["data"]> {
private toolCallId: string;
constructor(socket: Socket, messageId: string, contentId: string, toolCallId: string) {
super(socket, messageId, contentId, "toolcall");
this.toolCallId = toolCallId;
}
// 追加参数块
appendArgs(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, args: chunk },
strategy: "append",
status: "streaming",
});
return this;
}
// 追加结果块
appendResult(chunk: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, chunk },
strategy: "append",
status: "streaming",
});
return this;
}
// 设置完整结果
setResult(result: string) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, result },
strategy: "merge",
status: "complete",
});
return this;
}
// 更新事件类型
updateEventType(eventType: ToolCallContent["data"]["eventType"]) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "toolcall",
data: { toolCallId: this.toolCallId, eventType },
strategy: "merge",
status: "streaming",
});
return this;
}
}
// 推理构建器
class ReasoningBuilder {
private socket: Socket;
private messageId: string;
private contentId: string;
constructor(socket: Socket, messageId: string, contentId: string) {
this.socket = socket;
this.messageId = messageId;
this.contentId = contentId;
}
// 添加子内容
addContent(content: AIMessageContent) {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "reasoning",
data: [content],
strategy: "append",
status: "streaming",
});
return this;
}
// 完成推理
complete() {
this.socket.emit("content:update", {
messageId: this.messageId,
contentId: this.contentId,
type: "reasoning",
status: "complete",
});
return this;
}
}
export default ResTool;
export { MessageBuilder, ContentStream, ThinkingStream, SearchStream, ToolCallStream, ReasoningBuilder };

View File

@ -107,7 +107,7 @@ class MessageBuilder {
const content: TextContent = {
type: "text",
id: contentId,
data: initialText,
data: "",
status: "pending",
};
@ -116,7 +116,11 @@ class MessageBuilder {
content,
});
return new ContentStream<string>(this.socket, this.messageId, contentId, "text");
const stream = new AutoThinkingTextStream(this.socket, this.messageId, contentId, this);
if (initialText) {
stream.append(initialText);
}
return stream;
}
// 添加 Markdown 内容
@ -393,6 +397,154 @@ class ThinkingStream extends ContentStream<ThinkingContent["data"]> {
}
}
// 文本内容流:自动把 <think>...</think> 转为 thinking 内容
class AutoThinkingTextStream extends ContentStream<string> {
private static readonly OPEN_TAG = "<think>";
private static readonly CLOSE_TAG = "</think>";
private readonly messageBuilder: MessageBuilder;
private pending = "";
private inThinking = false;
private thinkingStream: ThinkingStream | null = null;
private thinkingBuffer = "";
private thinkingStartTime: number = 0;
constructor(socket: Socket, messageId: string, contentId: string, messageBuilder: MessageBuilder) {
super(socket, messageId, contentId, "text");
this.messageBuilder = messageBuilder;
}
/**
* str tag
* 0
*/
private static tailPrefixLen(str: string, tag: string): number {
const maxCheck = Math.min(str.length, tag.length - 1);
for (let len = maxCheck; len >= 1; len--) {
if (str.endsWith(tag.slice(0, len))) {
return len;
}
}
return 0;
}
override append(chunk: string) {
if (!chunk) return this;
let rest = this.pending + chunk;
this.pending = "";
while (rest.length > 0) {
if (!this.inThinking) {
// 寻找 <think> 开始标签
const openIndex = rest.indexOf(AutoThinkingTextStream.OPEN_TAG);
if (openIndex >= 0) {
this.flushText(rest.slice(0, openIndex));
this.inThinking = true;
this.thinkingStartTime = Date.now();
this.thinkingBuffer = "";
this.ensureThinkingStream();
rest = rest.slice(openIndex + AutoThinkingTextStream.OPEN_TAG.length);
continue;
}
// 检查尾部是否可能是标签的部分前缀
const keep = AutoThinkingTextStream.tailPrefixLen(rest, AutoThinkingTextStream.OPEN_TAG);
if (keep > 0) {
this.flushText(rest.slice(0, rest.length - keep));
this.pending = rest.slice(rest.length - keep);
} else {
this.flushText(rest);
}
break;
} else {
// 寻找 </think> 结束标签
const closeIndex = rest.indexOf(AutoThinkingTextStream.CLOSE_TAG);
if (closeIndex >= 0) {
this.flushThinking(rest.slice(0, closeIndex));
this.finishThinking();
rest = rest.slice(closeIndex + AutoThinkingTextStream.CLOSE_TAG.length);
continue;
}
// 检查尾部是否可能是标签的部分前缀
const keep = AutoThinkingTextStream.tailPrefixLen(rest, AutoThinkingTextStream.CLOSE_TAG);
if (keep > 0) {
this.flushThinking(rest.slice(0, rest.length - keep));
this.pending = rest.slice(rest.length - keep);
} else {
this.flushThinking(rest);
}
break;
}
}
return this;
}
override complete(finalData?: string) {
if (finalData) {
this.append(finalData);
}
if (this.pending) {
if (this.inThinking) {
this.flushThinking(this.pending);
} else {
this.flushText(this.pending);
}
this.pending = "";
}
this.finishThinking();
super.complete();
return this;
}
override error() {
if (this.thinkingStream) {
this.thinkingStream.error();
this.thinkingStream = null;
}
this.pending = "";
this.thinkingBuffer = "";
this.inThinking = false;
return super.error();
}
/** 输出普通文本 */
private flushText(text: string) {
if (!text) return;
super.append(text);
}
/** 输出思考文本:累积完整内容,用 merge 策略发送,避免前端 append 丢失 */
private flushThinking(text: string) {
if (!text) return;
this.thinkingBuffer += text;
this.ensureThinkingStream().merge({ title: "思考中...", text: this.thinkingBuffer });
}
private ensureThinkingStream() {
if (!this.thinkingStream) {
this.thinkingStartTime = Date.now();
this.thinkingStream = this.messageBuilder.thinking("思考中...");
}
return this.thinkingStream;
}
private finishThinking() {
if (this.thinkingStream) {
const elapsed = ((Date.now() - this.thinkingStartTime) / 1000).toFixed(1);
this.thinkingStream.updateTitle(`思考完毕(${elapsed}秒)`);
this.thinkingStream.complete({ title: `思考完毕(${elapsed}秒)`, text: this.thinkingBuffer });
this.thinkingStream = null;
this.thinkingBuffer = "";
}
this.inThinking = false;
}
}
// 搜索内容流
class SearchStream extends ContentStream<SearchContent["data"]> {
constructor(socket: Socket, messageId: string, contentId: string) {

View File

@ -1,19 +1,6 @@
<<<<<<< HEAD
// @db-hash 93b2462070c45c2b449e9a18c4e88763
//该文件由脚本自动生成,请勿手动修改
=======
// @db-hash f7bc2fdb80756d5536929eb47155578b
//该文件由脚本自动生成,请勿手动修改
export interface _o_script_old_20260327 {
'content'?: string | null;
'createTime'?: number | null;
'id'?: number;
'name'?: string | null;
'projectId'?: number | null;
}
>>>>>>> 9da2610cdedc1e293b351ed3ab67fbc6fcd989f1
export interface memories {
'content': string;
'createTime': number;
@ -60,6 +47,7 @@ export interface o_assets {
'name'?: string | null;
'projectId'?: number | null;
'prompt'?: string | null;
'promptState'?: string | null;
'remark'?: string | null;
'scriptId'?: number | null;
'startTime'?: number | null;
@ -179,7 +167,7 @@ export interface o_storyboard {
'filePath'?: string | null;
'frameMode'?: string | null;
'id'?: number;
'index'?: string | null;
'index'?: number | null;
'lines'?: string | null;
'mode'?: string | null;
'model'?: string | null;
@ -244,10 +232,6 @@ export interface o_videoConfig {
}
export interface DB {
<<<<<<< HEAD
=======
"_o_script_old_20260327": _o_script_old_20260327;
>>>>>>> 9da2610cdedc1e293b351ed3ab67fbc6fcd989f1
"memories": memories;
"o_agentDeploy": o_agentDeploy;
"o_agentWorkData": o_agentWorkData;

View File

@ -4,6 +4,7 @@ import path from "path";
import isPathInside from "is-path-inside";
import getPath from "@/utils/getPath";
import * as fs from "fs";
import fg from "fast-glob";
type SkillAttribution =
//剧本Agent
@ -18,13 +19,13 @@ type SkillAttribution =
| "production_agent_supervision";
interface SkillInput {
mainSkill: SkillAttribution;
mainSkill: SkillAttribution[];
workspace?: string[];
attachedSkills?: string[];
}
interface SkillPaths {
mainSkill: string;
mainSkill: { path: string; name: string; description: string }[];
secondarySkills: string[];
tertiarySkills: string[];
}
@ -40,7 +41,7 @@ function ensureNonEmptyBody(body: string, fallback: string): string {
// ==================== 解析 SKILL.md ====================
function parseFrontmatter(content: string): { name: string; description: string } {
export function parseFrontmatter(content: string): { name: string; description: string } {
const match = content.match(/^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
if (!match?.[1]) {
throw new Error(`技能文件缺少有效的 frontmatter确保以 --- 包裹并包含 name 和 description 字段。${content}`);
@ -121,9 +122,16 @@ export async function useSkill(input: SkillInput) {
const { mainSkill, workspace = [], attachedSkills = [] } = input;
const rootDir = getPath("skills");
const normalizedRootDir = path.resolve(rootDir);
const mainPath = path.join(rootDir, mainSkill + ".md");
if (!fs.existsSync(mainPath)) throw new Error(`主技能文件不存在: ${mainPath}`);
if (!isPathInside(mainPath, normalizedRootDir)) throw new Error(`技能名称无效:检测到路径穿越。${mainPath}`);
const mainSkills: { path: string; name: string; description: string }[] = [];
for (const skill of mainSkill) {
const skillPath = path.join(rootDir, skill + ".md");
if (!fs.existsSync(skillPath)) throw new Error(`主技能文件不存在: ${skillPath}`);
if (!isPathInside(skillPath, normalizedRootDir)) throw new Error(`技能名称无效:检测到路径穿越。${skillPath}`);
const content = await fs.promises.readFile(skillPath, "utf-8");
const parsed = parseFrontmatter(content);
mainSkills.push({ path: skillPath, ...parsed });
}
const resolveSafeSkillDir = (dir: string): string | null => {
const resolvedDir = path.resolve(normalizedRootDir, dir);
@ -147,50 +155,52 @@ export async function useSkill(input: SkillInput) {
});
const skillPaths: SkillPaths = {
mainSkill: mainPath,
mainSkill: mainSkills,
secondarySkills: collectMdFiles(workspace, false),
tertiarySkills: collectMdFiles(attachedSkills, true),
};
const content = await fs.promises.readFile(mainPath, "utf-8");
const skill = parseFrontmatter(content);
return { prompt: buildPrompt(skill), tools: createSkillTools(skill, skillPaths), skillPaths };
return { prompt: buildSkillPrompt(mainSkills), tools: createSkillTools(mainSkills, skillPaths), skillPaths };
}
function buildPrompt(skill: { name: string; description: string }): string {
export function buildSkillPrompt(skills: { name: string; description: string }[]): string {
const skillEntries = skills
.map((s) => ` <skill>\n <name>${s.name}</name>\n <description>${s.description}</description>\n </skill>`)
.join("\n");
return `## Skills
activate_skill
read_skill_file
<available_skills>
<skill>
<name>${skill.name}</name>
<description>${skill.description}</description>
</skill>
${skillEntries}
</available_skills>`;
}
function createSkillTools(skill: { name: string; description: string }, skillPaths: SkillPaths) {
export function createSkillTools(skills: { name: string; description: string }[], skillPaths: SkillPaths) {
const activated = new Set<string>(); // 已激活技能集合,防止重复加载
const skillsRootDir = path.resolve(getPath("skills"));
const skillNames = skills.map((s) => s.name);
const skillMap = new Map(skillPaths.mainSkill.map((s) => [s.name, s]));
return {
activate_skill: tool({
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skill.name}`,
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skillNames.join(", ")}`,
inputSchema: z.object({
name: z.enum([skill.name] as [string, ...string[]]).describe("要激活的技能名称"),
name: z.enum(skillNames as [string, ...string[]]).describe("要激活的技能名称"),
}),
execute: async ({ name }) => {
if (activated.has(name)) {
console.log(`⚡[主技能] 技能 "${name}" 已激活,跳过重复注入`);
return { alreadyActive: true, message: `技能 "${name}" 已激活,无需重复加载` };
}
const matched = skillMap.get(name);
if (!matched) return { error: `未找到技能 "${name}"` };
let raw = "";
try {
raw = await fs.promises.readFile(skillPaths.mainSkill, "utf-8");
console.log(`⚡[主技能] ✓ 已读取主技能文件: ${skillPaths.mainSkill}${raw.length} 字符)`);
raw = await fs.promises.readFile(matched.path, "utf-8");
console.log(`⚡[主技能] ✓ 已读取主技能文件: ${matched.path}${raw.length} 字符)`);
} catch (error) {
console.log(`⚡[主技能] ✗ 读取失败:未找到文件 "${skillPaths.mainSkill}"`);
console.log(`⚡[主技能] ✗ 读取失败:未找到文件 "${matched.path}"`);
}
activated.add(name);
console.log(`⚡[主技能] ✓ 技能 "${name}" 已激活`);
@ -253,3 +263,12 @@ function createSkillTools(skill: { name: string; description: string }, skillPat
}),
};
}
export async function scanSkills(folderPath: string) {
const unixPath = toUnixPath(folderPath);
const entries = await fg(unixPath, {
onlyFiles: true,
absolute: true,
});
return entries;
}

View File

@ -116,7 +116,6 @@ class AiImage {
return withTaskRecord(this.key, input.taskClass, input.describe, input.relatedObjects, input.projectId, async (modelName) => {
const fn = await getVendorTemplateFn("imageRequest", modelName);
this.result = await fn(input);
console.log("%c Line:119 🌽 this.result", "background:#ed9ec7", this.result);
if (this.result.startsWith("http")) this.result = await urlToBase64(this.result);
return this;
});

View File

@ -1,7 +1,7 @@
import { EventEmitter } from "events";
import { o_novel } from "@/types/database";
import { useSkill } from "@/utils/agent/skillsTools";
import u from "@/utils";
import { stripThink } from "@/utils/stripThink";
export interface EventType {
id: number;
event: string;
@ -24,11 +24,11 @@ class CleanNovel {
this.concurrency = concurrency;
}
private async processChapter(novel: o_novel, intansce: ReturnType<typeof u.Ai.Text>): Promise<EventType | null> {
private async processChapter(novel: o_novel): Promise<EventType | null> {
try {
const prompt = await u.getPrompts("event");
const data = await u.db("o_prompt").where("type", "eventExtraction").first("data");
const resData = await intansce.invoke({
const resData = await u.Ai.Text("universalAi").invoke({
system: data ? JSON.stringify(data.data) : (prompt as string),
messages: [
{
@ -45,7 +45,7 @@ class CleanNovel {
},
],
});
const preData = resData.text;
const preData = stripThink(resData.text);
this.emitter.emit("item", { id: novel.id, event: preData });
return { id: novel.id!, event: preData };
} catch (e) {
@ -56,7 +56,6 @@ class CleanNovel {
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
const totalEvent: EventType[] = [];
const intansce = u.Ai.Text("universalAi");
// 并发控制:通过信号量限制同时执行的任务数
let running = 0;
@ -68,7 +67,7 @@ class CleanNovel {
const novel = allChapters[index++];
running++;
return this.processChapter(novel, intansce).then((result) => {
return this.processChapter(novel).then((result) => {
if (result) totalEvent.push(result);
running--;
return runNext();

View File

@ -9,6 +9,7 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createAnthropic } from "@ai-sdk/anthropic";
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createXai } from "@ai-sdk/xai";
import { createMinimax } from "vercel-minimax-ai-provider";
import FormData from "form-data";
export default function runCode(code: string) {
@ -24,6 +25,7 @@ export default function runCode(code: string) {
createAnthropic,
createOpenAICompatible,
createXai,
createMinimax,
createGoogleGenerativeAI,
zipImage,
zipImageResolution,

View File

@ -7,6 +7,14 @@
resolved "https://registry.npmmirror.com/7zip-bin/-/7zip-bin-5.2.0.tgz#7a03314684dd6572b7dfa89e68ce31d60286854d"
integrity sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==
"@ai-sdk/anthropic@3.0.6":
version "3.0.6"
resolved "https://registry.npmmirror.com/@ai-sdk/anthropic/-/anthropic-3.0.6.tgz#155909d705efe3f82c72153b68291c3a2557397c"
integrity sha512-Ns5OOPHXbODzitvqCySnAFZCAm9ldpx+fdbC0c/f9QwX5b4MQtQJIQ0xZyKm+tB/ynBoeV6zhtyWDXjYeVEWIw==
dependencies:
"@ai-sdk/provider" "3.0.1"
"@ai-sdk/provider-utils" "4.0.3"
"@ai-sdk/anthropic@^3.0.35":
version "3.0.64"
resolved "https://registry.npmmirror.com/@ai-sdk/anthropic/-/anthropic-3.0.64.tgz#755e310e74a4ab364108df39e491d7fa9c5f6bd3"
@ -74,6 +82,24 @@
"@standard-schema/spec" "^1.1.0"
eventsource-parser "^3.0.6"
"@ai-sdk/provider-utils@4.0.3":
version "4.0.3"
resolved "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.3.tgz#0487848465b016de37e0b216184cbbd161d014e6"
integrity sha512-Vo2p61dDld8Dy/O66zKQpE4nqHojiEEYEjZcSbICjE7h8Z6QmHzBfd+ss/paIDdyXyS0yHmC1GoRYYKo89cqZQ==
dependencies:
"@ai-sdk/provider" "3.0.1"
"@standard-schema/spec" "^1.1.0"
eventsource-parser "^3.0.6"
"@ai-sdk/provider-utils@4.0.4":
version "4.0.4"
resolved "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-4.0.4.tgz#b2f5af446f152be64124725677a900be615c8766"
integrity sha512-VxhX0B/dWGbpNHxrKCWUAJKXIXV015J4e7qYjdIU9lLWeptk0KMLGcqkB4wFxff5Njqur8dt8wRi1MN9lZtDqg==
dependencies:
"@ai-sdk/provider" "3.0.2"
"@standard-schema/spec" "^1.1.0"
eventsource-parser "^3.0.6"
"@ai-sdk/provider-utils@^3.0.0":
version "3.0.22"
resolved "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-3.0.22.tgz#fc9824f5a5c290a95c14888de130b02e52020060"
@ -90,6 +116,20 @@
dependencies:
json-schema "^0.4.0"
"@ai-sdk/provider@3.0.1":
version "3.0.1"
resolved "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.1.tgz#5bd8809910fc401f024c7784a77eb116171d0296"
integrity sha512-2lR4w7mr9XrydzxBSjir4N6YMGdXD+Np1Sh0RXABh7tWdNFFwIeRI1Q+SaYZMbfL8Pg8RRLcrxQm51yxTLhokg==
dependencies:
json-schema "^0.4.0"
"@ai-sdk/provider@3.0.2":
version "3.0.2"
resolved "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.2.tgz#d4ee0b53e2c0b2a1b3e36f7356844fda53e63487"
integrity sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==
dependencies:
json-schema "^0.4.0"
"@ai-sdk/provider@3.0.7":
version "3.0.7"
resolved "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-3.0.7.tgz#470bb8f9e46ec9d8d62b07b4c1f5737b991ebe83"
@ -5302,6 +5342,15 @@ vary@^1, vary@^1.1.2:
resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
vercel-minimax-ai-provider@^0.0.2:
version "0.0.2"
resolved "https://registry.npmmirror.com/vercel-minimax-ai-provider/-/vercel-minimax-ai-provider-0.0.2.tgz#84192a8a86b756b23904ad9c5127c9132817b987"
integrity sha512-h9QzLL7RBmOreqWfr2fcoFVNTJgusENJVagVm8vAi+DBfd+1t+sVJZ/hAhKrtuCKCrm33BlOSWVdJehQFju5jQ==
dependencies:
"@ai-sdk/anthropic" "3.0.6"
"@ai-sdk/provider" "3.0.2"
"@ai-sdk/provider-utils" "4.0.4"
verror@^1.10.0:
version "1.10.1"
resolved "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb"