Add ChapterKind for front/back matter chapters
Chapters can now be marked FrontMatter/Body/BackMatter. Number stays the manuscript sort key for every chapter; the author-facing display number is now computed per-request as the chapter's ordinal among Body chapters only, so a foreword or afterword no longer shifts the numbering of the rest of the book. Surfaced through the API, agent toolset, and import toolset.
This commit is contained in:
@@ -385,7 +385,12 @@ public class NovelAgentToolset(
|
||||
"list_chapters",
|
||||
"List the novel's chapters in manuscript order with beat and word counts.",
|
||||
new JsonSchemaBuilder().Build(),
|
||||
async (novelId, _, ct) => (await chapters.ListAsync(novelId, ct)).Select(c => c.ToSummaryResponse()));
|
||||
async (novelId, _, ct) =>
|
||||
{
|
||||
var list = await chapters.ListAsync(novelId, ct);
|
||||
var displayNumbers = ChapterNumbering.DisplayNumbers(list);
|
||||
return list.Select(c => c.ToSummaryResponse(displayNumbers.TryGetValue(c.Id, out var n) ? n : null));
|
||||
});
|
||||
|
||||
yield return new AgentTool(
|
||||
"get_chapter",
|
||||
@@ -396,15 +401,25 @@ public class NovelAgentToolset(
|
||||
async (_, input, ct) =>
|
||||
{
|
||||
var chapterId = JsonInput.RequiredGuid(input, "chapter_id");
|
||||
return await OrNotFound(chapters.GetAsync(chapterId, ct), c => c.ToResponse(), "Chapter", chapterId);
|
||||
var chapter = await chapters.GetAsync(chapterId, ct);
|
||||
if (chapter is null)
|
||||
{
|
||||
return new ToolNotFound("Chapter", chapterId);
|
||||
}
|
||||
|
||||
var displayNumber = await chapters.DisplayNumberAsync(chapter, ct);
|
||||
return chapter.ToResponse(displayNumber);
|
||||
});
|
||||
|
||||
yield return new AgentTool(
|
||||
"create_chapter",
|
||||
"Add a chapter. Its number is appended to the end of the manuscript unless you supply one.",
|
||||
"Add a chapter. Its number is appended to the end of the manuscript unless you supply one. "
|
||||
+ "Front matter (foreword, introduction, prologue) and back matter (afterword, about the "
|
||||
+ "author) are labeled by title alone and do not count against the numbered chapters.",
|
||||
new JsonSchemaBuilder()
|
||||
.Str("title", "Chapter title.", required: true)
|
||||
.Int("number", "Position in the manuscript, 1-based.")
|
||||
.Int("number", "Manuscript position, 1-based, counting front and back matter.")
|
||||
.Enum("kind", "Front matter, a numbered body chapter, or back matter. Defaults to a body chapter.", System.Enum.GetNames<ChapterKind>())
|
||||
.Str("summary", "What the chapter covers.")
|
||||
.StringArray("locations", "Where and when the chapter takes place. Unknown locations are created.")
|
||||
.Str("notes", "Anything else worth recording.")
|
||||
@@ -413,26 +428,39 @@ public class NovelAgentToolset(
|
||||
.Str("prose", "The chapter's drafted text, in markdown, if you are writing it now.")
|
||||
.StringArray("tags", "Tags for cross-referencing. Replaces the existing tags.")
|
||||
.Build(),
|
||||
async (novelId, input, ct) => await OrNotFound(chapters.CreateAsync(novelId, new CreateChapterRequest(
|
||||
JsonInput.RequiredString(input, "title"),
|
||||
JsonInput.Int(input, "number"),
|
||||
JsonInput.String(input, "summary"),
|
||||
JsonInput.Strings(input, "locations"),
|
||||
JsonInput.String(input, "notes"),
|
||||
JsonInput.Enum<DraftStatus>(input, "status") ?? DraftStatus.Planned,
|
||||
JsonInput.Int(input, "target_word_count"),
|
||||
JsonInput.String(input, "prose"),
|
||||
JsonInput.Strings(input, "tags")), ct), c => c.ToResponse(), "Novel", novelId));
|
||||
async (novelId, input, ct) =>
|
||||
{
|
||||
var chapter = await chapters.CreateAsync(novelId, new CreateChapterRequest(
|
||||
JsonInput.RequiredString(input, "title"),
|
||||
JsonInput.Int(input, "number"),
|
||||
JsonInput.Enum<ChapterKind>(input, "kind") ?? ChapterKind.Body,
|
||||
JsonInput.String(input, "summary"),
|
||||
JsonInput.Strings(input, "locations"),
|
||||
JsonInput.String(input, "notes"),
|
||||
JsonInput.Enum<DraftStatus>(input, "status") ?? DraftStatus.Planned,
|
||||
JsonInput.Int(input, "target_word_count"),
|
||||
JsonInput.String(input, "prose"),
|
||||
JsonInput.Strings(input, "tags")), ct);
|
||||
|
||||
if (chapter is null)
|
||||
{
|
||||
return new ToolNotFound("Novel", novelId);
|
||||
}
|
||||
|
||||
var displayNumber = await chapters.DisplayNumberAsync(chapter, ct);
|
||||
return chapter.ToResponse(displayNumber);
|
||||
});
|
||||
|
||||
yield return new AgentTool(
|
||||
"update_chapter",
|
||||
"Revise a chapter's title, number, summary, locations, notes, status or drafted "
|
||||
"Revise a chapter's title, number, kind, summary, locations, notes, status or drafted "
|
||||
+ "prose. Use 'prose' to write or replace the chapter's draft text in markdown; the "
|
||||
+ "word count is recomputed automatically.",
|
||||
new JsonSchemaBuilder()
|
||||
.Str("chapter_id", "Id of the chapter to update.", required: true)
|
||||
.Str("title", "New title.")
|
||||
.Int("number", "Position in the manuscript.")
|
||||
.Int("number", "Manuscript position, 1-based, counting front and back matter.")
|
||||
.Enum("kind", "Front matter, a numbered body chapter, or back matter.", System.Enum.GetNames<ChapterKind>())
|
||||
.Str("summary", "What the chapter covers.")
|
||||
.StringArray("locations", "Where and when the chapter takes place. Replaces the existing locations. Unknown locations are created.")
|
||||
.Str("notes", "Anything else worth recording.")
|
||||
@@ -444,18 +472,27 @@ public class NovelAgentToolset(
|
||||
async (_, input, ct) =>
|
||||
{
|
||||
var chapterId = JsonInput.RequiredGuid(input, "chapter_id");
|
||||
return await OrNotFound(chapters.UpdateAsync(
|
||||
var chapter = await chapters.UpdateAsync(
|
||||
chapterId,
|
||||
new UpdateChapterRequest(
|
||||
JsonInput.String(input, "title"),
|
||||
JsonInput.Int(input, "number"),
|
||||
JsonInput.Enum<ChapterKind>(input, "kind"),
|
||||
JsonInput.String(input, "summary"),
|
||||
JsonInput.Strings(input, "locations"),
|
||||
JsonInput.String(input, "notes"),
|
||||
JsonInput.Enum<DraftStatus>(input, "status"),
|
||||
JsonInput.Int(input, "target_word_count"),
|
||||
JsonInput.String(input, "prose"),
|
||||
JsonInput.Strings(input, "tags")), ct), c => c.ToResponse(), "Chapter", chapterId);
|
||||
JsonInput.Strings(input, "tags")), ct);
|
||||
|
||||
if (chapter is null)
|
||||
{
|
||||
return new ToolNotFound("Chapter", chapterId);
|
||||
}
|
||||
|
||||
var displayNumber = await chapters.DisplayNumberAsync(chapter, ct);
|
||||
return chapter.ToResponse(displayNumber);
|
||||
});
|
||||
|
||||
yield return new AgentTool(
|
||||
|
||||
Reference in New Issue
Block a user