|
|
@@ -337,57 +337,29 @@ function appendToolEvents(
|
|
|
}), session);
|
|
|
}
|
|
|
|
|
|
-export async function runInteractionRequest(params: {
|
|
|
- readonly session: InteractionSession;
|
|
|
- readonly request: InteractionRequest;
|
|
|
- readonly tools: InteractionRuntimeTools;
|
|
|
-}): Promise<InteractionRuntimeResult> {
|
|
|
- const request = routeInteractionRequest(params.request);
|
|
|
- const language = resolveRuntimeLanguage(request);
|
|
|
- let session = params.session;
|
|
|
- const addEvent = (
|
|
|
+interface RuntimeRequestHelpers {
|
|
|
+ readonly language: RuntimeLanguage;
|
|
|
+ readonly addEvent: (
|
|
|
nextSession: InteractionSession,
|
|
|
kind: string,
|
|
|
status: InteractionEvent["status"],
|
|
|
detail: string,
|
|
|
- ): InteractionSession => appendInteractionEvent(nextSession, {
|
|
|
- kind,
|
|
|
- timestamp: Date.now(),
|
|
|
- status,
|
|
|
- bookId: nextSession.activeBookId,
|
|
|
- chapterNumber: nextSession.activeChapterNumber,
|
|
|
- detail,
|
|
|
- });
|
|
|
-
|
|
|
- if (request.mode) {
|
|
|
- session = updateAutomationMode(session, request.mode as AutomationMode);
|
|
|
- }
|
|
|
-
|
|
|
- session = clearPendingDecision({
|
|
|
- ...session,
|
|
|
- currentExecution: buildTaskStartedState(session, request, language),
|
|
|
- });
|
|
|
- session = addEvent(session, "task.started", session.currentExecution!.status, localize(language, {
|
|
|
- zh: `开始执行 ${request.intent}。`,
|
|
|
- en: `Started ${request.intent}.`,
|
|
|
- }));
|
|
|
+ ) => InteractionSession;
|
|
|
+ readonly markCompleted: (nextSession: InteractionSession) => InteractionSession;
|
|
|
+}
|
|
|
|
|
|
- const markCompleted = (nextSession: InteractionSession): InteractionSession => ({
|
|
|
- ...nextSession,
|
|
|
- currentExecution: {
|
|
|
- status: "completed",
|
|
|
- bookId: nextSession.activeBookId,
|
|
|
- chapterNumber: nextSession.activeChapterNumber,
|
|
|
- stageLabel: localize(language, {
|
|
|
- zh: "已完成",
|
|
|
- en: "completed",
|
|
|
- }),
|
|
|
- },
|
|
|
- });
|
|
|
+async function handleDraftLifecycleRequest(params: {
|
|
|
+ readonly session: InteractionSession;
|
|
|
+ readonly request: InteractionRequest;
|
|
|
+ readonly tools: InteractionRuntimeTools;
|
|
|
+ readonly helpers: RuntimeRequestHelpers;
|
|
|
+}): Promise<InteractionRuntimeResult | undefined> {
|
|
|
+ const { session, request, tools, helpers } = params;
|
|
|
+ const { language, addEvent, markCompleted } = helpers;
|
|
|
|
|
|
switch (request.intent) {
|
|
|
case "develop_book": {
|
|
|
- if (!params.tools.developBookDraft) {
|
|
|
+ if (!tools.developBookDraft) {
|
|
|
throw new Error(localize(language, {
|
|
|
zh: "创作草案会话暂未实现。",
|
|
|
en: "Book-draft ideation is not implemented yet.",
|
|
|
@@ -399,7 +371,7 @@ export async function runInteractionRequest(params: {
|
|
|
en: "Book-draft ideation requires user input.",
|
|
|
}));
|
|
|
}
|
|
|
- const toolResult = await params.tools.developBookDraft(request.instruction, session.creationDraft);
|
|
|
+ const toolResult = await tools.developBookDraft(request.instruction, session.creationDraft);
|
|
|
const metadata = extractToolMetadata(toolResult);
|
|
|
const draft = metadata.details?.creationDraft as InteractionSession["creationDraft"] | undefined;
|
|
|
if (!draft) {
|
|
|
@@ -408,11 +380,10 @@ export async function runInteractionRequest(params: {
|
|
|
en: "Book-draft tool did not return draft data.",
|
|
|
}));
|
|
|
}
|
|
|
- session = updateCreationDraft(session, draft);
|
|
|
- session = appendToolEvents(session, metadata.events);
|
|
|
+ const nextSession = appendToolEvents(updateCreationDraft(session, draft), metadata.events);
|
|
|
const completed = {
|
|
|
- ...markCompleted(session),
|
|
|
- currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution,
|
|
|
+ ...markCompleted(nextSession),
|
|
|
+ currentExecution: metadata.currentExecution ?? markCompleted(nextSession).currentExecution,
|
|
|
};
|
|
|
return {
|
|
|
session: addEvent(completed, "task.completed", "completed", localize(language, {
|
|
|
@@ -442,7 +413,7 @@ export async function runInteractionRequest(params: {
|
|
|
};
|
|
|
}
|
|
|
case "create_book": {
|
|
|
- if (!params.tools.createBook) {
|
|
|
+ if (!tools.createBook) {
|
|
|
throw new Error(localize(language, {
|
|
|
zh: "交互运行时暂未实现创建作品。",
|
|
|
en: "Book creation is not implemented in the interaction runtime yet.",
|
|
|
@@ -456,7 +427,7 @@ export async function runInteractionRequest(params: {
|
|
|
en: "Book creation requires a title.",
|
|
|
}));
|
|
|
}
|
|
|
- const toolResult = await params.tools.createBook({
|
|
|
+ const toolResult = await tools.createBook({
|
|
|
title,
|
|
|
genre: request.genre ?? effectiveDraft?.genre,
|
|
|
platform: request.platform ?? effectiveDraft?.platform,
|
|
|
@@ -485,11 +456,13 @@ export async function runInteractionRequest(params: {
|
|
|
en: "Create-book tool did not return a book id.",
|
|
|
}));
|
|
|
}
|
|
|
- session = clearCreationDraft(bindActiveBook(session, createdBookId));
|
|
|
- session = appendToolEvents(session, metadata.events);
|
|
|
+ const nextSession = appendToolEvents(
|
|
|
+ clearCreationDraft(bindActiveBook(session, createdBookId)),
|
|
|
+ metadata.events,
|
|
|
+ );
|
|
|
const completed = {
|
|
|
- ...markCompleted(session),
|
|
|
- currentExecution: metadata.currentExecution ?? markCompleted(session).currentExecution,
|
|
|
+ ...markCompleted(nextSession),
|
|
|
+ currentExecution: metadata.currentExecution ?? markCompleted(nextSession).currentExecution,
|
|
|
};
|
|
|
return {
|
|
|
session: addEvent(completed, "task.completed", "completed", localize(language, {
|
|
|
@@ -516,6 +489,146 @@ export async function runInteractionRequest(params: {
|
|
|
}),
|
|
|
};
|
|
|
}
|
|
|
+ default:
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleBookSelectionRequest(params: {
|
|
|
+ readonly session: InteractionSession;
|
|
|
+ readonly request: InteractionRequest;
|
|
|
+ readonly tools: InteractionRuntimeTools;
|
|
|
+ readonly helpers: RuntimeRequestHelpers;
|
|
|
+}): Promise<InteractionRuntimeResult | undefined> {
|
|
|
+ const { session, request, tools, helpers } = params;
|
|
|
+ const { language, addEvent, markCompleted } = helpers;
|
|
|
+
|
|
|
+ switch (request.intent) {
|
|
|
+ case "list_books": {
|
|
|
+ const books = await tools.listBooks();
|
|
|
+ const completed = markCompleted(session);
|
|
|
+ return {
|
|
|
+ session: addEvent(completed, "task.completed", "completed", localize(language, {
|
|
|
+ zh: `已列出 ${books.length} 本作品。`,
|
|
|
+ en: `Listed ${books.length} book(s).`,
|
|
|
+ })),
|
|
|
+ responseText: books.length > 0
|
|
|
+ ? localize(language, {
|
|
|
+ zh: `作品列表:${books.join("、")}`,
|
|
|
+ en: `Books: ${books.join(", ")}`,
|
|
|
+ })
|
|
|
+ : localize(language, {
|
|
|
+ zh: "当前项目下没有作品。",
|
|
|
+ en: "No books found in this project.",
|
|
|
+ }),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ case "select_book": {
|
|
|
+ if (!request.bookId) {
|
|
|
+ throw new Error(localize(language, {
|
|
|
+ zh: "切换作品需要提供作品 ID。",
|
|
|
+ en: "Book selection requires a book id.",
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ const books = await tools.listBooks();
|
|
|
+ if (!books.includes(request.bookId)) {
|
|
|
+ throw new Error(localize(language, {
|
|
|
+ zh: `当前项目中找不到作品「${request.bookId}」。`,
|
|
|
+ en: `Book "${request.bookId}" not found in this project.`,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ const completed = markCompleted(bindActiveBook(session, request.bookId));
|
|
|
+ return {
|
|
|
+ session: addEvent(completed, "task.completed", "completed", localize(language, {
|
|
|
+ zh: `已切换当前作品到 ${request.bookId}。`,
|
|
|
+ en: `Bound active book to ${request.bookId}.`,
|
|
|
+ })),
|
|
|
+ responseText: localize(language, {
|
|
|
+ zh: `当前作品:${request.bookId}`,
|
|
|
+ en: `Active book: ${request.bookId}`,
|
|
|
+ }),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export async function runInteractionRequest(params: {
|
|
|
+ readonly session: InteractionSession;
|
|
|
+ readonly request: InteractionRequest;
|
|
|
+ readonly tools: InteractionRuntimeTools;
|
|
|
+}): Promise<InteractionRuntimeResult> {
|
|
|
+ const request = routeInteractionRequest(params.request);
|
|
|
+ const language = resolveRuntimeLanguage(request);
|
|
|
+ let session = params.session;
|
|
|
+ const addEvent = (
|
|
|
+ nextSession: InteractionSession,
|
|
|
+ kind: string,
|
|
|
+ status: InteractionEvent["status"],
|
|
|
+ detail: string,
|
|
|
+ ): InteractionSession => appendInteractionEvent(nextSession, {
|
|
|
+ kind,
|
|
|
+ timestamp: Date.now(),
|
|
|
+ status,
|
|
|
+ bookId: nextSession.activeBookId,
|
|
|
+ chapterNumber: nextSession.activeChapterNumber,
|
|
|
+ detail,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (request.mode) {
|
|
|
+ session = updateAutomationMode(session, request.mode as AutomationMode);
|
|
|
+ }
|
|
|
+
|
|
|
+ session = clearPendingDecision({
|
|
|
+ ...session,
|
|
|
+ currentExecution: buildTaskStartedState(session, request, language),
|
|
|
+ });
|
|
|
+ session = addEvent(session, "task.started", session.currentExecution!.status, localize(language, {
|
|
|
+ zh: `开始执行 ${request.intent}。`,
|
|
|
+ en: `Started ${request.intent}.`,
|
|
|
+ }));
|
|
|
+
|
|
|
+ const markCompleted = (nextSession: InteractionSession): InteractionSession => ({
|
|
|
+ ...nextSession,
|
|
|
+ currentExecution: {
|
|
|
+ status: "completed",
|
|
|
+ bookId: nextSession.activeBookId,
|
|
|
+ chapterNumber: nextSession.activeChapterNumber,
|
|
|
+ stageLabel: localize(language, {
|
|
|
+ zh: "已完成",
|
|
|
+ en: "completed",
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const helperContext: RuntimeRequestHelpers = {
|
|
|
+ language,
|
|
|
+ addEvent,
|
|
|
+ markCompleted,
|
|
|
+ };
|
|
|
+
|
|
|
+ const draftLifecycleResult = await handleDraftLifecycleRequest({
|
|
|
+ session,
|
|
|
+ request,
|
|
|
+ tools: params.tools,
|
|
|
+ helpers: helperContext,
|
|
|
+ });
|
|
|
+ if (draftLifecycleResult) {
|
|
|
+ return draftLifecycleResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ const bookSelectionResult = await handleBookSelectionRequest({
|
|
|
+ session,
|
|
|
+ request,
|
|
|
+ tools: params.tools,
|
|
|
+ helpers: helperContext,
|
|
|
+ });
|
|
|
+ if (bookSelectionResult) {
|
|
|
+ return bookSelectionResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (request.intent) {
|
|
|
case "write_next":
|
|
|
case "continue_book": {
|
|
|
const bookId = request.bookId ?? session.activeBookId;
|
|
|
@@ -563,51 +676,6 @@ export async function runInteractionRequest(params: {
|
|
|
),
|
|
|
};
|
|
|
}
|
|
|
- case "list_books": {
|
|
|
- const books = await params.tools.listBooks();
|
|
|
- const completed = markCompleted(session);
|
|
|
- return {
|
|
|
- session: addEvent(completed, "task.completed", "completed", localize(language, {
|
|
|
- zh: `已列出 ${books.length} 本作品。`,
|
|
|
- en: `Listed ${books.length} book(s).`,
|
|
|
- })),
|
|
|
- responseText: books.length > 0
|
|
|
- ? localize(language, {
|
|
|
- zh: `作品列表:${books.join("、")}`,
|
|
|
- en: `Books: ${books.join(", ")}`,
|
|
|
- })
|
|
|
- : localize(language, {
|
|
|
- zh: "当前项目下没有作品。",
|
|
|
- en: "No books found in this project.",
|
|
|
- }),
|
|
|
- };
|
|
|
- }
|
|
|
- case "select_book": {
|
|
|
- if (!request.bookId) {
|
|
|
- throw new Error(localize(language, {
|
|
|
- zh: "切换作品需要提供作品 ID。",
|
|
|
- en: "Book selection requires a book id.",
|
|
|
- }));
|
|
|
- }
|
|
|
- const books = await params.tools.listBooks();
|
|
|
- if (!books.includes(request.bookId)) {
|
|
|
- throw new Error(localize(language, {
|
|
|
- zh: `当前项目中找不到作品「${request.bookId}」。`,
|
|
|
- en: `Book "${request.bookId}" not found in this project.`,
|
|
|
- }));
|
|
|
- }
|
|
|
- const completed = markCompleted(bindActiveBook(session, request.bookId));
|
|
|
- return {
|
|
|
- session: addEvent(completed, "task.completed", "completed", localize(language, {
|
|
|
- zh: `已切换当前作品到 ${request.bookId}。`,
|
|
|
- en: `Bound active book to ${request.bookId}.`,
|
|
|
- })),
|
|
|
- responseText: localize(language, {
|
|
|
- zh: `当前作品:${request.bookId}`,
|
|
|
- en: `Active book: ${request.bookId}`,
|
|
|
- }),
|
|
|
- };
|
|
|
- }
|
|
|
case "revise_chapter":
|
|
|
case "rewrite_chapter": {
|
|
|
const bookId = request.bookId ?? session.activeBookId;
|