@musnows/scriverse 0.5.2 → 0.5.4

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
@@ -28,7 +28,7 @@ import { currentRequestActor, runWithRequestActor } from "./request-context.js";
28
28
  import { APP_VERSION } from "./version.js";
29
29
  import { fullWorkModulePermissions, proseReplacementPermissionModules } from "./work-permissions.js";
30
30
  import { CollaborationPresence, editorPageKey, entityEditorPageKey, modulePageKey, pageLabelForKey, presencePageKinds } from "./collaboration-presence.js";
31
- import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, setSessionCookie, UserAuthService } from "./user-auth.js";
31
+ import { clearSessionCookie, createCliApiScopeMiddleware, createUserSessionMiddleware, createWorkAuthorizationMiddleware, relationshipAnalysisReadModules, setSessionCookie, UserAuthService } from "./user-auth.js";
32
32
  const nonEmpty = z.string().trim().min(1);
33
33
  const identifier = z.string().trim().min(1).max(200);
34
34
  const optionalStrings = z.array(z.string()).optional();
@@ -274,6 +274,7 @@ const providerSchema = z.object({
274
274
  name: nonEmpty.max(200),
275
275
  baseUrl: z.string().url().refine((value) => value.startsWith("http://") || value.startsWith("https://"), "接口地址必须使用 HTTP 或 HTTPS"),
276
276
  apiKey: nonEmpty.max(10_000),
277
+ protocol: z.enum(["openai-chat-completions", "anthropic-messages"]).optional(),
277
278
  status: z.enum(["enabled", "disabled"]).optional(),
278
279
  note: z.string().max(10_000).optional(),
279
280
  concurrencyLimit: z.number().int().min(1).max(100).optional(),
@@ -379,8 +380,8 @@ const relationshipAnalysisScopeSchema = z.object({
379
380
  }
380
381
  });
381
382
  const analysisTaskSchema = z.union([
382
- z.object({ taskType: z.literal("relationship-analysis"), scope: relationshipAnalysisScopeSchema.optional() }).strict(),
383
- z.object({ taskType: analysisTaskTypeSchema, scope: jsonObject.optional() }).strict().superRefine((input, context) => {
383
+ z.object({ taskType: z.literal("relationship-analysis"), scope: relationshipAnalysisScopeSchema.optional(), modelId: identifier.optional() }).strict(),
384
+ z.object({ taskType: analysisTaskTypeSchema, scope: jsonObject.optional(), modelId: identifier.optional() }).strict().superRefine((input, context) => {
384
385
  if (input.scope?.includeAllSettings !== undefined) {
385
386
  context.addIssue({ code: z.ZodIssueCode.custom, path: ["scope", "includeAllSettings"], message: "包含所有设定仅支持人物关系分析" });
386
387
  }
@@ -570,7 +571,24 @@ export function createRuntime(options) {
570
571
  return auth.workModulePermissions(request.authUser, resolvedWorkId, request.authMethod !== "api-key") ?? fullWorkModulePermissions();
571
572
  };
572
573
  const captcha = new ImageCaptchaService({ revealAnswer: options.revealCaptchaAnswer === true });
573
- const ai = new AiManager(store, new CredentialVault(options.masterSecret), options.fetchImpl ?? fetch, options.security ? (url) => assertSafeAiEndpoint(url, options.security?.allowPrivateAiEndpoints) : undefined);
574
+ const ai = new AiManager(store, new CredentialVault(options.masterSecret), options.fetchImpl ?? fetch, options.security ? (url) => assertSafeAiEndpoint(url, options.security?.allowPrivateAiEndpoints) : undefined, (task, actor) => {
575
+ if (task.taskType !== "relationship-analysis")
576
+ return;
577
+ const requiredModules = relationshipAnalysisReadModules(task.scope);
578
+ if (requiredModules.length === 0)
579
+ return;
580
+ const creator = actor ? null : database.get("SELECT created_by_user_id FROM analysis_tasks WHERE id = ?", String(task.id));
581
+ const userId = actor?.userId ?? (typeof creator?.created_by_user_id === "string" ? creator.created_by_user_id : null);
582
+ if (!userId)
583
+ return;
584
+ const user = auth.getUser(userId);
585
+ if (user.status !== "active")
586
+ throw new AppError(403, "WORK_ACCESS_DENIED", "任务创建者已无法访问这部作品");
587
+ auth.assertWorkAccess(user, String(task.workId), {
588
+ read: requiredModules,
589
+ write: ["ai-analysis"]
590
+ }, false, actor?.allowAdminAccess ?? false);
591
+ });
574
592
  const app = express();
575
593
  const upload = multer({
576
594
  storage: multer.memoryStorage(),
@@ -601,6 +619,7 @@ export function createRuntime(options) {
601
619
  status: "ok",
602
620
  version: APP_VERSION,
603
621
  protocol: "openai-chat-completions",
622
+ protocols: ["openai-chat-completions", "anthropic-messages"],
604
623
  development: options.developmentServer === true
605
624
  });
606
625
  });
@@ -1417,9 +1436,22 @@ export function createRuntime(options) {
1417
1436
  });
1418
1437
  app.post("/api/works/:workId/tasks", (request, response) => {
1419
1438
  const input = parse(analysisTaskSchema, request.body);
1420
- data(response, redactTaskCharacterNames(store.createTask(request.params.workId, input), requestPermissions(request, request.params.workId)), 201);
1439
+ data(response, redactTaskCharacterNames(ai.createTask(request.params.workId, input), requestPermissions(request, request.params.workId)), 201);
1421
1440
  });
1422
1441
  app.post("/api/works/:workId/tasks/auto-run", (request, response) => {
1442
+ const permissions = requestPermissions(request, request.params.workId);
1443
+ for (const taskId of store.listOldestPendingTaskIds(request.params.workId, store.countPendingTasks(request.params.workId))) {
1444
+ const task = store.getTask(taskId);
1445
+ if (task.taskType !== "relationship-analysis")
1446
+ continue;
1447
+ const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1448
+ if (deniedModules.length > 0) {
1449
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取待运行定向人物关系分析所需资料模块的权限", {
1450
+ taskId,
1451
+ modules: deniedModules
1452
+ });
1453
+ }
1454
+ }
1423
1455
  data(response, ai.startAutoRunBatch(request.params.workId));
1424
1456
  });
1425
1457
  app.get("/api/tasks/:taskId/detail", (request, response) => data(response, redactTaskCharacterNames(store.getTaskDetail(request.params.taskId), requestPermissions(request))));
@@ -1438,7 +1470,20 @@ export function createRuntime(options) {
1438
1470
  app.get("/api/tasks/:taskId/trace/calls/:callId", (request, response) => data(response, ai.getTaskTraceCall(request.params.taskId, request.params.callId)));
1439
1471
  app.post("/api/tasks/:taskId/run", async (request, response) => {
1440
1472
  const input = parse(z.object({ modelId: identifier.optional() }), request.body ?? {});
1441
- data(response, redactTaskCharacterNames(await ai.runTask(request.params.taskId, input.modelId), requestPermissions(request)));
1473
+ const task = store.getTask(request.params.taskId);
1474
+ if (task.taskType === "relationship-analysis") {
1475
+ const permissions = requestPermissions(request, String(task.workId));
1476
+ const deniedModules = relationshipAnalysisReadModules(task.scope).filter((module) => permissions[module] === "none");
1477
+ if (deniedModules.length > 0) {
1478
+ throw new AppError(403, "WORK_MODULE_READ_DENIED", "你没有读取本次定向人物关系分析所需资料模块的权限", {
1479
+ modules: deniedModules
1480
+ });
1481
+ }
1482
+ }
1483
+ data(response, redactTaskCharacterNames(await ai.runTask(request.params.taskId, input.modelId, request.authUser ? {
1484
+ userId: request.authUser.userId,
1485
+ allowAdminAccess: request.authMethod !== "api-key"
1486
+ } : undefined), requestPermissions(request)));
1442
1487
  });
1443
1488
  app.post("/api/tasks/:taskId/cancel", (request, response) => data(response, redactTaskCharacterNames(ai.cancelTask(request.params.taskId), requestPermissions(request))));
1444
1489
  app.get("/api/platform/ai/providers", (request, response) => {