project.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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 FoundationConfigSchema = z.object({
  70. reviewRetries: z.number().int().min(0).max(10).default(2),
  71. });
  72. export type FoundationConfig = z.infer<typeof FoundationConfigSchema>;
  73. export const AgentLLMOverrideSchema = z.object({
  74. model: z.string().min(1),
  75. provider: z.enum(["anthropic", "openai", "custom"]).optional(),
  76. baseUrl: z.string().url().optional(),
  77. apiKeyEnv: z.string().optional(),
  78. stream: z.boolean().optional(),
  79. });
  80. export type AgentLLMOverride = z.infer<typeof AgentLLMOverrideSchema>;
  81. export const InputGovernanceModeSchema = z.enum(["legacy", "v2"]);
  82. export type InputGovernanceMode = z.infer<typeof InputGovernanceModeSchema>;
  83. const ModelOverrideValueSchema = z.union([z.string(), AgentLLMOverrideSchema]);
  84. export const ProjectConfigSchema = z.object({
  85. name: z.string().min(1),
  86. version: z.literal("0.1.0"),
  87. language: z.enum(["zh", "en"]).default("zh"),
  88. llm: LLMConfigSchema,
  89. notify: z.array(NotifyChannelSchema).default([]),
  90. detection: DetectionConfigSchema.optional(),
  91. foundation: FoundationConfigSchema.default({
  92. reviewRetries: 2,
  93. }),
  94. modelOverrides: z.record(z.string(), ModelOverrideValueSchema).optional(),
  95. inputGovernanceMode: InputGovernanceModeSchema.default("v2"),
  96. daemon: z.object({
  97. schedule: z.object({
  98. radarCron: z.string().default("0 */6 * * *"),
  99. writeCron: z.string().default("*/15 * * * *"),
  100. }),
  101. maxConcurrentBooks: z.number().int().min(1).default(3),
  102. chaptersPerCycle: z.number().int().min(1).max(20).default(1),
  103. retryDelayMs: z.number().int().min(0).default(30_000),
  104. cooldownAfterChapterMs: z.number().int().min(0).default(10_000),
  105. maxChaptersPerDay: z.number().int().min(1).default(50),
  106. qualityGates: QualityGatesSchema.default({
  107. maxAuditRetries: 2,
  108. pauseAfterConsecutiveFailures: 3,
  109. retryTemperatureStep: 0.1,
  110. }),
  111. }).default({
  112. schedule: {
  113. radarCron: "0 */6 * * *",
  114. writeCron: "*/15 * * * *",
  115. },
  116. maxConcurrentBooks: 3,
  117. chaptersPerCycle: 1,
  118. retryDelayMs: 30_000,
  119. cooldownAfterChapterMs: 10_000,
  120. maxChaptersPerDay: 50,
  121. qualityGates: {
  122. maxAuditRetries: 2,
  123. pauseAfterConsecutiveFailures: 3,
  124. retryTemperatureStep: 0.1,
  125. },
  126. }),
  127. });
  128. export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;