project.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { z } from "zod";
  2. // C1 (v2.0.0 breaking): `maxTokens` 字段已被 providers bank 接管;zod 用 strip mode 静默丢弃老配置里的 `maxTokens`。
  3. const LLMServiceEntrySchema = z.object({
  4. service: z.string().min(1),
  5. name: z.string().min(1).optional(),
  6. baseUrl: z.string().url().optional(),
  7. temperature: z.number().min(0).max(2).optional(),
  8. apiFormat: z.enum(["chat", "responses"]).optional(),
  9. stream: z.boolean().optional(),
  10. });
  11. // C1 (v2.0.0 breaking): 删除 maxTokens / maxTokensCap 字段。
  12. // 每个模型的真实 maxOutput 来自 providers/<name>.ts 的 InkosModel.maxOutput;
  13. // 老配置里写的 maxTokens / maxTokensCap 会被 zod strip 静默丢弃(不报错)。
  14. export const LLMConfigSchema = z.object({
  15. provider: z.enum(["anthropic", "openai", "custom"]),
  16. service: z.string().default("custom"),
  17. configSource: z.enum(["env", "studio"]).default("env"),
  18. baseUrl: z.string().url(),
  19. apiKey: z.string().default(""),
  20. model: z.string().min(1),
  21. temperature: z.number().min(0).max(2).default(0.7),
  22. thinkingBudget: z.number().int().min(0).default(0),
  23. extra: z.record(z.unknown()).optional(),
  24. headers: z.record(z.string()).optional(),
  25. apiFormat: z.enum(["chat", "responses"]).default("chat"),
  26. stream: z.boolean().default(true),
  27. services: z.array(LLMServiceEntrySchema).optional(),
  28. defaultModel: z.string().min(1).optional(),
  29. });
  30. export type LLMConfig = z.infer<typeof LLMConfigSchema>;
  31. export const NotifyChannelSchema = z.discriminatedUnion("type", [
  32. z.object({
  33. type: z.literal("telegram"),
  34. botToken: z.string().min(1),
  35. chatId: z.string().min(1),
  36. }),
  37. z.object({
  38. type: z.literal("wechat-work"),
  39. webhookUrl: z.string().url(),
  40. }),
  41. z.object({
  42. type: z.literal("feishu"),
  43. webhookUrl: z.string().url(),
  44. }),
  45. z.object({
  46. type: z.literal("webhook"),
  47. url: z.string().url(),
  48. secret: z.string().optional(),
  49. events: z.array(z.string()).default([]),
  50. }),
  51. ]);
  52. export type NotifyChannel = z.infer<typeof NotifyChannelSchema>;
  53. export const DetectionConfigSchema = z.object({
  54. provider: z.enum(["gptzero", "originality", "custom"]).default("custom"),
  55. apiUrl: z.string().url(),
  56. apiKeyEnv: z.string().min(1),
  57. threshold: z.number().min(0).max(1).default(0.5),
  58. enabled: z.boolean().default(false),
  59. autoRewrite: z.boolean().default(false),
  60. maxRetries: z.number().int().min(1).max(10).default(3),
  61. });
  62. export type DetectionConfig = z.infer<typeof DetectionConfigSchema>;
  63. export const QualityGatesSchema = z.object({
  64. maxAuditRetries: z.number().int().min(0).max(10).default(2),
  65. pauseAfterConsecutiveFailures: z.number().int().min(1).default(3),
  66. retryTemperatureStep: z.number().min(0).max(0.5).default(0.1),
  67. });
  68. export type QualityGates = z.infer<typeof QualityGatesSchema>;
  69. export const AgentLLMOverrideSchema = z.object({
  70. model: z.string().min(1),
  71. provider: z.enum(["anthropic", "openai", "custom"]).optional(),
  72. baseUrl: z.string().url().optional(),
  73. apiKeyEnv: z.string().optional(),
  74. stream: z.boolean().optional(),
  75. });
  76. export type AgentLLMOverride = z.infer<typeof AgentLLMOverrideSchema>;
  77. export const InputGovernanceModeSchema = z.enum(["legacy", "v2"]);
  78. export type InputGovernanceMode = z.infer<typeof InputGovernanceModeSchema>;
  79. const ModelOverrideValueSchema = z.union([z.string(), AgentLLMOverrideSchema]);
  80. export const ProjectConfigSchema = z.object({
  81. name: z.string().min(1),
  82. version: z.literal("0.1.0"),
  83. language: z.enum(["zh", "en"]).default("zh"),
  84. llm: LLMConfigSchema,
  85. notify: z.array(NotifyChannelSchema).default([]),
  86. detection: DetectionConfigSchema.optional(),
  87. modelOverrides: z.record(z.string(), ModelOverrideValueSchema).optional(),
  88. inputGovernanceMode: InputGovernanceModeSchema.default("v2"),
  89. daemon: z.object({
  90. schedule: z.object({
  91. radarCron: z.string().default("0 */6 * * *"),
  92. writeCron: z.string().default("*/15 * * * *"),
  93. }),
  94. maxConcurrentBooks: z.number().int().min(1).default(3),
  95. chaptersPerCycle: z.number().int().min(1).max(20).default(1),
  96. retryDelayMs: z.number().int().min(0).default(30_000),
  97. cooldownAfterChapterMs: z.number().int().min(0).default(10_000),
  98. maxChaptersPerDay: z.number().int().min(1).default(50),
  99. qualityGates: QualityGatesSchema.default({
  100. maxAuditRetries: 2,
  101. pauseAfterConsecutiveFailures: 3,
  102. retryTemperatureStep: 0.1,
  103. }),
  104. }).default({
  105. schedule: {
  106. radarCron: "0 */6 * * *",
  107. writeCron: "*/15 * * * *",
  108. },
  109. maxConcurrentBooks: 3,
  110. chaptersPerCycle: 1,
  111. retryDelayMs: 30_000,
  112. cooldownAfterChapterMs: 10_000,
  113. maxChaptersPerDay: 50,
  114. qualityGates: {
  115. maxAuditRetries: 2,
  116. pauseAfterConsecutiveFailures: 3,
  117. retryTemperatureStep: 0.1,
  118. },
  119. }),
  120. });
  121. export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;