@musnows/scriverse 0.5.12 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/app.js CHANGED
@@ -103,7 +103,7 @@ const presenceHeartbeatSchema = z.object({
103
103
  page: z.discriminatedUnion("kind", [
104
104
  z.object({ kind: z.literal(presencePageKinds[0]) }).strict(),
105
105
  z.object({ kind: z.literal(presencePageKinds[1]), resourceId: identifier }).strict(),
106
- z.object({ kind: z.literal(presencePageKinds[2]), module: z.enum(["drafts", "settings", "characters", "races", "organizations", "timeline", "relationships", "outlines", "reviews", "tasks", "ai-settings"]) }).strict(),
106
+ z.object({ kind: z.literal(presencePageKinds[2]), module: z.enum(["drafts", "settings", "characters", "races", "organizations", "timeline", "comments", "relationships", "outlines", "reviews", "tasks", "ai-settings"]) }).strict(),
107
107
  z.object({ kind: z.literal(presencePageKinds[3]), module: z.enum(["setting", "character", "race", "organization", "relationship"]), resourceId: identifier.optional() }).strict(),
108
108
  z.object({ kind: z.literal(presencePageKinds[4]) }).strict()
109
109
  ])
@@ -293,15 +293,14 @@ const providerSchema = z.object({
293
293
  status: z.enum(["enabled", "disabled"]).optional(),
294
294
  note: z.string().max(10_000).optional(),
295
295
  concurrencyLimit: z.number().int().min(1).max(100).optional(),
296
- rpmLimit: z.number().int().min(1).max(10_000).optional(),
297
- maxTokens: z.number().int().min(1).max(32_768).optional()
296
+ rpmLimit: z.number().int().min(1).max(10_000).optional()
298
297
  });
299
298
  const modelSchema = z.object({
300
299
  displayName: nonEmpty.max(200),
301
300
  modelId: nonEmpty.max(300),
302
301
  purposes: optionalStrings,
303
302
  contextNote: z.string().max(10_000).optional(),
304
- contextWindow: z.number().int().min(1_024).max(2_000_000).optional(),
303
+ contextWindow: z.number().int().min(32_768, "模型上下文不能低于 32768 Token").max(2_000_000).optional(),
305
304
  outputNote: z.string().max(10_000).optional(),
306
305
  preset: jsonObject.optional(),
307
306
  thinkingEnabled: z.boolean().optional(),
@@ -323,6 +322,7 @@ const platformPageSizesSchema = z.object({
323
322
  timeline: z.number().int().min(10).max(100).optional(),
324
323
  outlines: z.number().int().min(10).max(100).optional(),
325
324
  relationships: z.number().int().min(10).max(100).optional(),
325
+ comments: z.number().int().min(10).max(100).optional(),
326
326
  reviews: z.number().int().min(10).max(100).optional(),
327
327
  analysisTasks: z.number().int().min(10).max(100).optional(),
328
328
  fileVersions: z.number().int().min(10).max(100).optional()
@@ -362,6 +362,15 @@ const aiProcessStepSchema = z.discriminatedUnion("type", [
362
362
  round: z.number().int().min(1).max(20),
363
363
  toolCall: aiToolCallResultSchema,
364
364
  createdAt: z.string().datetime({ offset: true })
365
+ }).strict(),
366
+ z.object({
367
+ id: z.string().min(1).max(300),
368
+ type: z.literal("context_compaction"),
369
+ round: z.number().int().min(1).max(20),
370
+ sourceMessageCount: z.number().int().min(1).max(100),
371
+ sourceChars: z.number().int().min(1).max(10_000_000),
372
+ summaryChars: z.number().int().min(1).max(1_000_000),
373
+ createdAt: z.string().datetime({ offset: true })
365
374
  }).strict()
366
375
  ]);
367
376
  const workAiSettingsSchema = z.object({
@@ -373,7 +382,8 @@ const workAiSettingsSchema = z.object({
373
382
  autoRunFailureThreshold: z.number().int().min(1).max(10).optional(),
374
383
  bookSummaryContextPercent: z.number().int().min(1).max(90).optional(),
375
384
  contextCompactThreshold: z.number().int().min(50).max(90).optional(),
376
- agentTools: z.array(z.enum(["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections", "search_drafts"])).max(6).optional()
385
+ agentTools: z.array(z.enum(["story_index", "read_chapters", "grep", "search_story_entities", "read_character_sections", "search_drafts"])).max(6).optional(),
386
+ titleGenerationModelId: z.string().trim().max(200).optional()
377
387
  }).strict();
378
388
  const contextSchema = z.object({
379
389
  type: z.enum(["none", "selection", "chapter", "volume", "book", "entities"]),
@@ -1049,6 +1059,12 @@ export function createRuntime(options) {
1049
1059
  data(response, pagination ? store.listChapterInsightsPage(request.params.chapterId, pagination) : store.listChapterInsights(request.params.chapterId));
1050
1060
  });
1051
1061
  app.get("/api/chapters/:chapterId/annotations", (request, response) => data(response, store.listChapterAnnotations(request.params.chapterId)));
1062
+ app.get("/api/works/:workId/chapter-annotations", (request, response) => {
1063
+ const pagination = parsePagination(request.query);
1064
+ data(response, pagination
1065
+ ? store.listWorkChapterAnnotationsPage(request.params.workId, pagination)
1066
+ : store.listWorkChapterAnnotations(request.params.workId));
1067
+ });
1052
1068
  app.post("/api/chapters/:chapterId/annotations", (request, response) => {
1053
1069
  const input = parse(z.object({
1054
1070
  kind: z.enum(["note", "todo"]),
@@ -1670,6 +1686,8 @@ export function createRuntime(options) {
1670
1686
  app.patch("/api/works/:workId/ai-settings", (request, response) => {
1671
1687
  const workId = request.params.workId;
1672
1688
  const input = parse(workAiSettingsSchema, request.body);
1689
+ if (input.titleGenerationModelId)
1690
+ ai.assertModelAvailable(input.titleGenerationModelId);
1673
1691
  const before = store.getWorkAiSettings(workId);
1674
1692
  let updated = store.updateWorkAiSettings(workId, input);
1675
1693
  if (updated.autoRunEnabled) {
@@ -1681,8 +1699,11 @@ export function createRuntime(options) {
1681
1699
  data(response, updated);
1682
1700
  });
1683
1701
  app.get("/api/works/:workId/ai-conversations", (request, response) => {
1684
- const pagination = parsePagination(request.query);
1685
- data(response, pagination ? store.listAiConversationsPage(request.params.workId, pagination) : store.listAiConversations(request.params.workId));
1702
+ const pagination = parsePagination({
1703
+ page: request.query.page ?? "1",
1704
+ limit: request.query.limit ?? "20"
1705
+ }) ?? { page: 1, limit: 20, offset: 0 };
1706
+ data(response, store.listAiConversationsPage(request.params.workId, pagination));
1686
1707
  });
1687
1708
  app.post("/api/works/:workId/ai-conversations", (request, response) => {
1688
1709
  const input = parse(z.object({ title: z.string().max(200).optional() }), request.body ?? {});
@@ -1732,32 +1753,23 @@ export function createRuntime(options) {
1732
1753
  app.post("/api/ai-conversations/:conversationId/compact", async (request, response) => {
1733
1754
  const input = parse(z.object({ modelId: identifier.optional(), scope: contextSchema }), request.body);
1734
1755
  const conversation = store.getAiConversation(request.params.conversationId);
1735
- data(response, await ai.compactConversation({
1756
+ const compacted = await ai.compactConversation({
1736
1757
  conversationId: request.params.conversationId,
1737
1758
  workId: String(conversation.workId),
1738
1759
  modelId: input.modelId,
1739
1760
  scope: input.scope
1740
- }));
1741
- });
1742
- app.post("/api/works/:workId/ai-context-usage", (request, response) => {
1743
- const input = parse(z.object({
1744
- modelId: identifier.optional(),
1745
- taskType: z.enum(TASK_TYPES).default("chat"),
1746
- scope: contextSchema,
1747
- instruction: z.string().max(100_000).default(""),
1748
- citations: aiCitationsSchema.optional(),
1749
- conversationId: identifier.optional(),
1750
- currentMessageId: identifier.optional()
1751
- }), request.body ?? {});
1752
- data(response, ai.getContextUsage({
1753
- workId: request.params.workId,
1754
- modelId: input.modelId,
1755
- taskType: input.taskType,
1756
- scope: input.scope,
1757
- instruction: instructionWithCitations(input.instruction, input.citations ?? []),
1758
- conversationId: input.conversationId,
1759
- excludeConversationMessageId: input.currentMessageId
1760
- }));
1761
+ });
1762
+ data(response, {
1763
+ ...compacted,
1764
+ usage: ai.getContextUsage({
1765
+ workId: String(conversation.workId),
1766
+ taskType: "chat",
1767
+ instruction: "",
1768
+ conversationId: request.params.conversationId,
1769
+ modelId: input.modelId,
1770
+ scope: input.scope
1771
+ })
1772
+ });
1761
1773
  });
1762
1774
  app.get("/api/works/:workId/providers", (request, response) => {
1763
1775
  store.getWork(request.params.workId);
@@ -1779,6 +1791,7 @@ export function createRuntime(options) {
1779
1791
  data(response, pagination ? ai.listModelsPage(request.params.providerId, pagination) : ai.listModels(request.params.providerId));
1780
1792
  });
1781
1793
  app.post("/api/providers/:providerId/models", (request, response) => data(response, ai.createModel(request.params.providerId, parse(modelSchema, request.body)), 201));
1794
+ app.post("/api/models/:modelId/test", async (request, response) => data(response, await ai.testModel(request.params.modelId)));
1782
1795
  app.get("/api/models/:modelId", (request, response) => data(response, ai.getModel(request.params.modelId)));
1783
1796
  app.patch("/api/models/:modelId", (request, response) => data(response, ai.updateModel(request.params.modelId, parse(modelSchema.partial(), request.body))));
1784
1797
  app.delete("/api/models/:modelId", (request, response) => {
@@ -1858,6 +1871,40 @@ export function createRuntime(options) {
1858
1871
  };
1859
1872
  sendEvent("ready", { streaming: true });
1860
1873
  try {
1874
+ const conversation = input.conversationId
1875
+ ? store.getAiConversationSummary(input.conversationId)
1876
+ : store.createAiConversation(request.params.workId);
1877
+ if (String(conversation.workId) !== request.params.workId) {
1878
+ throw new AppError(400, "CONVERSATION_WORK_MISMATCH", "AI 对话不属于当前作品");
1879
+ }
1880
+ const conversationId = String(conversation.id);
1881
+ const prepared = await ai.prepareConversationContext({
1882
+ conversationId,
1883
+ workId: request.params.workId,
1884
+ modelId: input.modelId,
1885
+ scope: input.scope,
1886
+ instruction: instructionWithCitations(input.instruction, citations),
1887
+ excludeConversationMessageId: input.currentMessageId
1888
+ });
1889
+ sendEvent("context", {
1890
+ ...prepared,
1891
+ conversation: {
1892
+ ...store.getAiConversationSummary(conversationId),
1893
+ contextWarningPending: prepared.action === "warn"
1894
+ }
1895
+ });
1896
+ if (prepared.action === "warn")
1897
+ return;
1898
+ const userMessage = input.currentMessageId
1899
+ ? null
1900
+ : store.addAiConversationMessage(conversationId, {
1901
+ role: "user",
1902
+ content: input.instruction,
1903
+ citations
1904
+ });
1905
+ const currentMessageId = input.currentMessageId ?? String(userMessage?.id ?? "");
1906
+ if (userMessage)
1907
+ sendEvent("user_message", { message: userMessage });
1861
1908
  const suggestion = await ai.createStreamingChat({
1862
1909
  workId: request.params.workId,
1863
1910
  instruction: instructionWithCitations(input.instruction, citations),
@@ -1865,9 +1912,10 @@ export function createRuntime(options) {
1865
1912
  signal: controller.signal,
1866
1913
  onToolCall: (toolCall, round) => sendEvent("tool_call", { ...toolCall, round }),
1867
1914
  onProcessStep: (step) => sendEvent("process_step", step),
1868
- conversationId: input.conversationId,
1869
- excludeConversationMessageId: input.currentMessageId,
1870
- ...(input.currentMessageId ? { assistantMessageRequestId: `assistant:${input.currentMessageId}` } : {}),
1915
+ onContextCompacted: (event) => sendEvent("context_compacted", event),
1916
+ conversationId,
1917
+ excludeConversationMessageId: currentMessageId,
1918
+ ...(currentMessageId ? { assistantMessageRequestId: `assistant:${currentMessageId}` } : {}),
1871
1919
  ...(input.modelId ? { modelId: input.modelId } : {}),
1872
1920
  ...(input.parameters ? { parameters: input.parameters } : {})
1873
1921
  }, (delta) => sendEvent("delta", { delta }));
@@ -1881,6 +1929,9 @@ export function createRuntime(options) {
1881
1929
  chapterVersion: suggestion.chapterVersion,
1882
1930
  toolCalls: suggestion.toolCalls,
1883
1931
  processSteps: suggestion.processSteps,
1932
+ contextUsage: suggestion.contextUsage,
1933
+ conversationId,
1934
+ conversationTitle: suggestion.conversationTitle,
1884
1935
  messageId: typeof suggestion.conversationMessage === "object" && suggestion.conversationMessage !== null
1885
1936
  ? suggestion.conversationMessage.id
1886
1937
  : undefined,
@@ -1891,9 +1942,19 @@ export function createRuntime(options) {
1891
1942
  }
1892
1943
  catch (error) {
1893
1944
  if (!controller.signal.aborted) {
1945
+ const details = error instanceof AppError && error.details && typeof error.details === "object" && !Array.isArray(error.details)
1946
+ ? error.details
1947
+ : null;
1894
1948
  sendEvent("error", {
1895
1949
  code: error instanceof AppError ? error.code : "AI_STREAM_FAILED",
1896
- message: error instanceof Error ? error.message : "AI 流式调用失败"
1950
+ message: error instanceof Error ? error.message : "AI 流式调用失败",
1951
+ ...(error instanceof AppError ? { status: error.status } : {}),
1952
+ ...(typeof details?.failure === "string" ? { failure: details.failure } : {}),
1953
+ ...(typeof details?.callId === "string" ? { callId: details.callId } : {}),
1954
+ ...(typeof details?.providerName === "string" ? { providerName: details.providerName } : {}),
1955
+ ...(typeof details?.providerId === "string" ? { providerId: details.providerId } : {}),
1956
+ ...(typeof details?.modelId === "string" ? { modelId: details.modelId } : {}),
1957
+ ...(typeof details?.modelRecordId === "string" ? { modelRecordId: details.modelRecordId } : {})
1897
1958
  });
1898
1959
  }
1899
1960
  }